summaryrefslogtreecommitdiff
path: root/services
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@localhost>2022-04-04 05:50:01 -0700
committerLinux Build Service Account <lnxbuild@localhost>2022-04-04 05:50:01 -0700
commit56d6467dd88fb3f1381097905594ae9da4454894 (patch)
tree184348b4e7763ab2058199fffad2cf48f78a9b3d /services
parent4503a44c57793f1f7262984c9c90c117f26d4f01 (diff)
parent5516a51432bbf0123788346e8cadd19e9243b768 (diff)
Merge 5516a51432bbf0123788346e8cadd19e9243b768 on remote branch
Change-Id: Ia4e41fcc006020e5b3a77070cbf8725ca4ea1f57
Diffstat (limited to 'services')
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java316
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java327
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java272
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java53
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java66
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java194
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java85
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickController.java13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java17
-rw-r--r--services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java30
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java55
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java11
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java72
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java46
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java2
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java12
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java13
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java2
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java58
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java2
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/BluetoothManagerService.java25
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java283
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java9
-rw-r--r--services/core/java/com/android/server/Watchdog.java2
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java27
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java103
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java40
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java6
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java65
-rw-r--r--services/core/java/com/android/server/am/BatteryExternalStatsWorker.java28
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java12
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java35
-rw-r--r--services/core/java/com/android/server/am/MeasuredEnergySnapshot.java11
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java23
-rw-r--r--services/core/java/com/android/server/am/PhantomProcessList.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java65
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java11
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java4
-rw-r--r--services/core/java/com/android/server/am/UserController.java91
-rw-r--r--services/core/java/com/android/server/am/UserState.java7
-rw-r--r--services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/apphibernation/AppHibernationService.java85
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java1
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java24
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java309
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java44
-rw-r--r--services/core/java/com/android/server/audio/RotationHelper.java35
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java986
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java6
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java16
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java96
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java19
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java62
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java31
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/EnrollClient.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java144
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java60
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java87
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java27
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java18
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java39
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java56
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java23
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java38
-rw-r--r--services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java6
-rw-r--r--services/core/java/com/android/server/broadcastradio/OWNERS3
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java17
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java5
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java9
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java17
-rw-r--r--services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java6
-rw-r--r--services/core/java/com/android/server/camera/CameraServiceProxy.java378
-rw-r--r--services/core/java/com/android/server/compat/CompatChange.java79
-rw-r--r--services/core/java/com/android/server/compat/CompatConfig.java409
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java10
-rw-r--r--services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java337
-rw-r--r--services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java430
-rw-r--r--services/core/java/com/android/server/compat/overrides/OWNERS2
-rw-r--r--services/core/java/com/android/server/compat/overrides/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java2
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java25
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceState.java33
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java529
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java33
-rw-r--r--services/core/java/com/android/server/devicestate/OverrideRequest.java58
-rw-r--r--services/core/java/com/android/server/devicestate/OverrideRequestController.java322
-rw-r--r--services/core/java/com/android/server/display/DeviceStateToLayoutMap.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayDevice.java45
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java11
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java234
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java7
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java40
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java17
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java7
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java45
-rw-r--r--services/core/java/com/android/server/display/PersistentDataStore.java79
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java28
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java75
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java77
-rw-r--r--services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceManager.java8
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java8
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java8
-rw-r--r--services/core/java/com/android/server/location/injector/LocationAttributionHelper.java99
-rw-r--r--services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java6
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java130
-rw-r--r--services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java36
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java17
-rw-r--r--services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java9
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java4
-rw-r--r--services/core/java/com/android/server/notification/BadgeExtractor.java7
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java44
-rw-r--r--services/core/java/com/android/server/notification/NotificationComparator.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java186
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java4
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java3
-rw-r--r--services/core/java/com/android/server/notification/VibratorHelper.java83
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java2
-rw-r--r--services/core/java/com/android/server/pm/OtaDexoptService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java53
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java55
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java5
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java7
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java3
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java64
-rw-r--r--services/core/java/com/android/server/policy/DisplayFoldController.java27
-rw-r--r--services/core/java/com/android/server/policy/KeyCombinationManager.java43
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java103
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java14
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java26
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java6
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java10
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java7
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsLogger.java5
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsService.java35
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java25
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java2
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java90
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java190
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java30
-rw-r--r--services/core/java/com/android/server/vcn/Vcn.java69
-rw-r--r--services/core/java/com/android/server/vcn/VcnGatewayConnection.java20
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java2
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationThread.java83
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java22
-rw-r--r--services/core/java/com/android/server/vr/Vr2dDisplay.java3
-rw-r--r--services/core/java/com/android/server/wallpaper/LocalColorRepository.java166
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java129
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java617
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java59
-rw-r--r--services/core/java/com/android/server/wm/ActivityInterceptorCallback.java98
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java252
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java1193
-rw-r--r--services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java28
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java29
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java286
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java67
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java279
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java330
-rw-r--r--services/core/java/com/android/server/wm/AnimationAdapter.java3
-rw-r--r--services/core/java/com/android/server/wm/AnrController.java44
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java10
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java287
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java420
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java35
-rw-r--r--services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java9
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java76
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java7
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java787
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java1086
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java33
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java8
-rw-r--r--services/core/java/com/android/server/wm/DockedTaskDividerController.java2
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java101
-rw-r--r--services/core/java/com/android/server/wm/DragState.java27
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java27
-rw-r--r--services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java100
-rw-r--r--services/core/java/com/android/server/wm/FadeRotationAnimationController.java19
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java11
-rw-r--r--services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java3
-rw-r--r--services/core/java/com/android/server/wm/InputManagerCallback.java5
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java102
-rw-r--r--services/core/java/com/android/server/wm/InputTarget.java40
-rw-r--r--services/core/java/com/android/server/wm/InputWindowHandleWrapper.java9
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java117
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java106
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java4
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java41
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java121
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java336
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java196
-rw-r--r--services/core/java/com/android/server/wm/LocalAnimationAdapter.java3
-rw-r--r--services/core/java/com/android/server/wm/LocaleOverlayHelper.java62
-rw-r--r--services/core/java/com/android/server/wm/LockTaskController.java48
-rw-r--r--services/core/java/com/android/server/wm/NavBarFadeAnimationController.java3
-rw-r--r--services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java38
-rw-r--r--services/core/java/com/android/server/wm/PackageConfigPersister.java93
-rw-r--r--services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java126
-rw-r--r--services/core/java/com/android/server/wm/PinnedTaskController.java40
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java24
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java13
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java181
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java80
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java151
-rw-r--r--services/core/java/com/android/server/wm/RunningTasks.java13
-rw-r--r--services/core/java/com/android/server/wm/SafeActivityOptions.java23
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java19
-rw-r--r--services/core/java/com/android/server/wm/Session.java31
-rw-r--r--services/core/java/com/android/server/wm/ShellRoot.java2
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java6
-rw-r--r--services/core/java/com/android/server/wm/StrictModeFlash.java3
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java51
-rw-r--r--services/core/java/com/android/server/wm/SurfaceFreezer.java124
-rw-r--r--services/core/java/com/android/server/wm/Task.java2781
-rwxr-xr-xservices/core/java/com/android/server/wm/TaskDisplayArea.java232
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java2541
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java624
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java44
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java344
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java93
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotLoader.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotSurface.java5
-rw-r--r--services/core/java/com/android/server/wm/Transition.java641
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java312
-rw-r--r--services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java25
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java90
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java13
-rw-r--r--services/core/java/com/android/server/wm/Watermark.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java398
-rw-r--r--services/core/java/com/android/server/wm/WindowContextListenerController.java78
-rw-r--r--services/core/java/com/android/server/wm/WindowFrames.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerDebugConfig.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java94
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java498
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java606
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java108
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java438
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowTracing.java3
-rw-r--r--services/core/jni/com_android_server_am_CachedAppOptimizer.cpp22
-rw-r--r--services/core/xsd/Android.bp2
-rw-r--r--services/core/xsd/device-state-config/device-state-config.xsd9
-rw-r--r--services/core/xsd/device-state-config/schema/current.txt7
-rw-r--r--services/core/xsd/display-layout-config/display-layout-config.xsd2
-rw-r--r--services/core/xsd/display-layout-config/schema/current.txt6
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java397
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java20
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java98
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java28
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java2
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt29
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt85
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java31
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/compat/OWNERS1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java269
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java726
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java129
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java14
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java134
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java42
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java31
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt55
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java33
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java42
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java135
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java2
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/AndroidTest.xml18
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java113
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java88
-rw-r--r--services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java218
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java132
-rw-r--r--services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java62
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java54
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java129
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/camera/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java88
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java78
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java76
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java230
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java55
-rw-r--r--services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java94
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt2
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java40
-rw-r--r--services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java20
-rw-r--r--services/tests/servicestests/test-apps/StubApp/Android.bp37
-rw-r--r--services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml25
-rw-r--r--services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java22
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java67
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java58
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java19
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java97
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java8
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java270
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java15
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java58
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java463
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java44
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java447
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java37
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java434
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java213
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java241
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java145
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java70
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java54
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java14
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java89
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java38
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java135
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java205
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java58
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java348
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/StubTransaction.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java526
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java152
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java112
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java117
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java221
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java64
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java159
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java85
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java41
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java85
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java258
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java154
-rwxr-xr-xservices/usb/java/com/android/server/usb/UsbDeviceManager.java9
-rw-r--r--services/usb/java/com/android/server/usb/UsbUserPermissionManager.java10
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java52
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java38
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java161
425 files changed, 29635 insertions, 9961 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index e9c9899023b1..ee80daeff87d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -21,8 +21,13 @@ import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILIT
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS;
import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP;
import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK;
+import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
@@ -31,6 +36,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
@@ -103,10 +109,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
FingerprintGestureDispatcher.FingerprintGestureClient {
private static final boolean DEBUG = false;
private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
- private static final String TRACE_A11Y_SERVICE_CONNECTION =
- LOG_TAG + ".IAccessibilityServiceConnection";
- private static final String TRACE_A11Y_SERVICE_CLIENT =
- LOG_TAG + ".IAccessibilityServiceClient";
+ private static final String TRACE_SVC_CONN = LOG_TAG + ".IAccessibilityServiceConnection";
+ private static final String TRACE_SVC_CLIENT = LOG_TAG + ".IAccessibilityServiceClient";
+ private static final String TRACE_WM = "WindowManagerInternal";
private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
protected static final String TAKE_SCREENSHOT = "takeScreenshot";
@@ -298,9 +303,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return false;
}
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onKeyEvent",
- keyEvent + ", " + sequenceNumber);
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber);
}
mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
} catch (RemoteException e) {
@@ -365,17 +369,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setOnKeyEventResult(boolean handled, int sequence) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setOnKeyEventResult",
- "handled=" + handled + ";sequence=" + sequence);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setOnKeyEventResult", "handled=" + handled + ";sequence=" + sequence);
}
mSystemSupport.getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
}
@Override
public AccessibilityServiceInfo getServiceInfo() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getServiceInfo");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getServiceInfo", "");
}
synchronized (mLock) {
return mAccessibilityServiceInfo;
@@ -393,8 +396,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setServiceInfo(AccessibilityServiceInfo info) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setServiceInfo", "info=" + info);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setServiceInfo", "info=" + info);
}
final long identity = Binder.clearCallingIdentity();
try {
@@ -421,8 +424,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Nullable
@Override
public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindows");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getWindows", "");
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -458,8 +461,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public AccessibilityWindowInfo getWindow(int windowId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindow", "windowId=" + windowId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getWindow", "windowId=" + windowId);
}
synchronized (mLock) {
int displayId = Display.INVALID_DISPLAY;
@@ -496,8 +499,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
long accessibilityNodeId, String viewIdResName, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByViewId",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("findAccessibilityNodeInfosByViewId",
"accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+ accessibilityNodeId + ";viewIdResName=" + viewIdResName + ";interactionId="
+ interactionId + ";callback=" + callback + ";interrogatingTid="
@@ -539,6 +542,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
+ if (intConnTracingEnabled()) {
+ logTraceIntConn("findAccessibilityNodeInfosByViewId",
+ accessibilityNodeId + ";" + viewIdResName + ";" + partialInteractiveRegion + ";"
+ + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid
+ + ";" + interrogatingTid + ";" + spec);
+ }
try {
connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId,
viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -564,8 +573,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfosByText",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("findAccessibilityNodeInfosByText",
"accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
+ accessibilityNodeId + ";text=" + text + ";interactionId=" + interactionId
+ ";callback=" + callback + ";interrogatingTid=" + interrogatingTid);
@@ -606,6 +615,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
+ if (intConnTracingEnabled()) {
+ logTraceIntConn("findAccessibilityNodeInfosByText",
+ accessibilityNodeId + ";" + text + ";" + partialInteractiveRegion + ";"
+ + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid
+ + ";" + interrogatingTid + ";" + spec);
+ }
try {
connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId,
text, partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -631,13 +646,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
int accessibilityWindowId, long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
long interrogatingTid, Bundle arguments) throws RemoteException {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(
- TRACE_A11Y_SERVICE_CONNECTION + ".findAccessibilityNodeInfoByAccessibilityId",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("findAccessibilityNodeInfoByAccessibilityId",
"accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
- + accessibilityNodeId + ";interactionId=" + interactionId + ";callback="
- + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid
- + ";arguments=" + arguments);
+ + accessibilityNodeId + ";interactionId=" + interactionId + ";callback="
+ + callback + ";flags=" + flags + ";interrogatingTid=" + interrogatingTid
+ + ";arguments=" + arguments);
}
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
@@ -675,6 +689,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
+ if (intConnTracingEnabled()) {
+ logTraceIntConn("findAccessibilityNodeInfoByAccessibilityId",
+ accessibilityNodeId + ";" + partialInteractiveRegion + ";" + interactionId + ";"
+ + callback + ";" + (mFetchFlags | flags) + ";" + interrogatingPid + ";"
+ + interrogatingTid + ";" + spec + ";" + arguments);
+ }
try {
connection.getRemote().findAccessibilityNodeInfoByAccessibilityId(
accessibilityNodeId, partialInteractiveRegion, interactionId, callback,
@@ -700,12 +720,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".findFocus",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("findFocus",
"accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
- + accessibilityNodeId + ";focusType=" + focusType + ";interactionId="
- + interactionId + ";callback=" + callback + ";interrogatingTid="
- + interrogatingTid);
+ + accessibilityNodeId + ";focusType=" + focusType + ";interactionId="
+ + interactionId + ";callback=" + callback + ";interrogatingTid="
+ + interrogatingTid);
}
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
@@ -743,6 +763,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
+ if (intConnTracingEnabled()) {
+ logTraceIntConn("findFocus",
+ accessibilityNodeId + ";" + focusType + ";" + partialInteractiveRegion + ";"
+ + interactionId + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid
+ + ";" + interrogatingTid + ";" + spec);
+ }
try {
connection.getRemote().findFocus(accessibilityNodeId, focusType,
partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -768,12 +794,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".focusSearch",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("focusSearch",
"accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
- + accessibilityNodeId + ";direction=" + direction + ";interactionId="
- + interactionId + ";callback=" + callback + ";interrogatingTid="
- + interrogatingTid);
+ + accessibilityNodeId + ";direction=" + direction + ";interactionId="
+ + interactionId + ";callback=" + callback + ";interrogatingTid="
+ + interrogatingTid);
}
final int resolvedWindowId;
RemoteAccessibilityConnection connection;
@@ -810,6 +836,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,
interrogatingPid, interrogatingTid);
final long identityToken = Binder.clearCallingIdentity();
+ if (intConnTracingEnabled()) {
+ logTraceIntConn("focusSearch",
+ accessibilityNodeId + ";" + direction + ";" + partialInteractiveRegion
+ + ";" + interactionId + ";" + callback + ";" + mFetchFlags + ";"
+ + interrogatingPid + ";" + interrogatingTid + ";" + spec);
+ }
try {
connection.getRemote().focusSearch(accessibilityNodeId, direction,
partialInteractiveRegion, interactionId, callback, mFetchFlags,
@@ -832,17 +864,17 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void sendGesture(int sequence, ParceledListSlice gestureSteps) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".sendGesture",
- "sequence=" + sequence + ";gestureSteps=" + gestureSteps);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn(
+ "sendGesture", "sequence=" + sequence + ";gestureSteps=" + gestureSteps);
}
}
@Override
public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".dispatchGesture", "sequence="
- + sequence + ";gestureSteps=" + gestureSteps + ";displayId=" + displayId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("dispatchGesture", "sequence=" + sequence + ";gestureSteps="
+ + gestureSteps + ";displayId=" + displayId);
}
}
@@ -851,12 +883,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
long accessibilityNodeId, int action, Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performAccessibilityAction",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("performAccessibilityAction",
"accessibilityWindowId=" + accessibilityWindowId + ";accessibilityNodeId="
- + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments
- + ";interactionId=" + interactionId + ";callback=" + callback
- + ";interrogatingTid=" + interrogatingTid);
+ + accessibilityNodeId + ";action=" + action + ";arguments=" + arguments
+ + ";interactionId=" + interactionId + ";callback=" + callback
+ + ";interrogatingTid=" + interrogatingTid);
}
final int resolvedWindowId;
synchronized (mLock) {
@@ -879,9 +911,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public boolean performGlobalAction(int action) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".performGlobalAction",
- "action=" + action);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("performGlobalAction", "action=" + action);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -893,8 +924,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSystemActions");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getSystemActions", "");
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -906,9 +937,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public boolean isFingerprintGestureDetectionAvailable() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(
- TRACE_A11Y_SERVICE_CONNECTION + ".isFingerprintGestureDetectionAvailable");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("isFingerprintGestureDetectionAvailable", "");
}
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
return false;
@@ -923,9 +953,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public float getMagnificationScale(int displayId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationScale",
- "displayId=" + displayId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getMagnificationScale", "displayId=" + displayId);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -942,9 +971,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public Region getMagnificationRegion(int displayId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationRegion",
- "displayId=" + displayId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getMagnificationRegion", "displayId=" + displayId);
}
synchronized (mLock) {
final Region region = Region.obtain();
@@ -970,9 +998,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public float getMagnificationCenterX(int displayId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterX",
- "displayId=" + displayId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getMagnificationCenterX", "displayId=" + displayId);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -996,9 +1023,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public float getMagnificationCenterY(int displayId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getMagnificationCenterY",
- "displayId=" + displayId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getMagnificationCenterY", "displayId=" + displayId);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -1032,9 +1058,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public boolean resetMagnification(int displayId, boolean animate) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".resetMagnification",
- "displayId=" + displayId + ";animate=" + animate);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("resetMagnification", "displayId=" + displayId + ";animate=" + animate);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -1058,10 +1083,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
float centerY, boolean animate) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationScaleAndCenter",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setMagnificationScaleAndCenter",
"displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
- + ";centerY=" + centerY + ";animate=" + animate);
+ + ";centerY=" + centerY + ";animate=" + animate);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -1087,8 +1112,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setMagnificationCallbackEnabled",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setMagnificationCallbackEnabled",
"displayId=" + displayId + ";enabled=" + enabled);
}
mInvocationHandler.setMagnificationCallbackEnabled(displayId, enabled);
@@ -1100,18 +1125,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setSoftKeyboardCallbackEnabled(boolean enabled) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardCallbackEnabled",
- "enabled=" + enabled);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setSoftKeyboardCallbackEnabled", "enabled=" + enabled);
}
mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
}
@Override
public void takeScreenshot(int displayId, RemoteCallback callback) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".takeScreenshot",
- "displayId=" + displayId + ";callback=" + callback);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("takeScreenshot", "displayId=" + displayId + ";callback=" + callback);
}
final long currentTimestamp = SystemClock.uptimeMillis();
if (mRequestTakeScreenshotTimestampMs != 0
@@ -1237,6 +1260,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final long identity = Binder.clearCallingIdentity();
try {
final IBinder overlayWindowToken = new Binder();
+ if (wmTracingEnabled()) {
+ logTraceWM("addWindowToken",
+ overlayWindowToken + ";TYPE_ACCESSIBILITY_OVERLAY;" + displayId + ";null");
+ }
mWindowManagerService.addWindowToken(overlayWindowToken, TYPE_ACCESSIBILITY_OVERLAY,
displayId, null /* options */);
synchronized (mLock) {
@@ -1263,6 +1290,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
*/
public void onDisplayRemoved(int displayId) {
final long identity = Binder.clearCallingIdentity();
+ if (wmTracingEnabled()) {
+ logTraceWM(
+ "addWindowToken", mOverlayWindowTokens.get(displayId) + ";true;" + displayId);
+ }
try {
mWindowManagerService.removeWindowToken(mOverlayWindowTokens.get(displayId), true,
displayId);
@@ -1282,9 +1313,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
*/
@Override
public IBinder getOverlayWindowToken(int displayId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getOverlayWindowToken",
- "displayId=" + displayId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getOverlayWindowToken", "displayId=" + displayId);
}
synchronized (mLock) {
return mOverlayWindowTokens.get(displayId);
@@ -1299,9 +1329,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
*/
@Override
public int getWindowIdForLeashToken(@NonNull IBinder token) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getWindowIdForLeashToken",
- "token=" + token);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getWindowIdForLeashToken", "token=" + token);
}
synchronized (mLock) {
return mA11yWindowManager.getWindowIdLocked(token);
@@ -1314,8 +1343,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// Clear the proxy in the other process so this
// IAccessibilityServiceConnection can be garbage collected.
if (mServiceInterface != null) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", "null, " + mId + ", null");
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("init", "null, " + mId + ", null");
}
mServiceInterface.init(null, mId, null);
}
@@ -1465,9 +1494,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityEvent",
- event + ";" + serviceWantsEvent);
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent);
}
listener.onAccessibilityEvent(event, serviceWantsEvent);
if (DEBUG) {
@@ -1522,9 +1550,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onMagnificationChanged", displayId
- + ", " + region + ", " + scale + ", " + centerX + ", " + centerY);
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", "
+ + scale + ", " + centerX + ", " + centerY);
}
listener.onMagnificationChanged(displayId, region, scale, centerX, centerY);
} catch (RemoteException re) {
@@ -1541,9 +1569,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSoftKeyboardShowModeChanged",
- String.valueOf(showState));
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState));
}
listener.onSoftKeyboardShowModeChanged(showState);
} catch (RemoteException re) {
@@ -1557,9 +1584,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonClicked",
- String.valueOf(displayId));
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId));
}
listener.onAccessibilityButtonClicked(displayId);
} catch (RemoteException re) {
@@ -1579,9 +1605,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(
- TRACE_A11Y_SERVICE_CLIENT + ".onAccessibilityButtonAvailabilityChanged",
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onAccessibilityButtonAvailabilityChanged",
String.valueOf(available));
}
listener.onAccessibilityButtonAvailabilityChanged(available);
@@ -1597,9 +1622,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onGesture",
- gestureInfo.toString());
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onGesture", gestureInfo.toString());
}
listener.onGesture(gestureInfo);
} catch (RemoteException re) {
@@ -1613,8 +1637,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onSystemActionsChanged");
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onSystemActionsChanged", "");
}
listener.onSystemActionsChanged();
} catch (RemoteException re) {
@@ -1628,8 +1652,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".clearAccessibilityCache");
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("clearAccessibilityCache", "");
}
listener.clearAccessibilityCache();
} catch (RemoteException re) {
@@ -1747,6 +1771,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
LocalServices.getService(ActivityTaskManagerInternal.class)
.setFocusedActivity(activityToken);
}
+ if (intConnTracingEnabled()) {
+ logTraceIntConn("performAccessibilityAction",
+ accessibilityNodeId + ";" + action + ";" + arguments + ";" + interactionId
+ + ";" + callback + ";" + mFetchFlags + ";" + interrogatingPid + ";"
+ + interrogatingTid);
+ }
connection.getRemote().performAccessibilityAction(accessibilityNodeId, action,
arguments, interactionId, callback, fetchFlags, interrogatingPid,
interrogatingTid);
@@ -1957,8 +1987,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setGestureDetectionPassthroughRegion",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setGestureDetectionPassthroughRegion",
"displayId=" + displayId + ";region=" + region);
}
mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region);
@@ -1966,8 +1996,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setTouchExplorationPassthroughRegion",
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setTouchExplorationPassthroughRegion",
"displayId=" + displayId + ";region=" + region);
}
mSystemSupport.setTouchExplorationPassthroughRegion(displayId, region);
@@ -1975,20 +2005,56 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
@Override
public void setFocusAppearance(int strokeWidth, int color) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setFocusAppearance",
- "strokeWidth=" + strokeWidth + ";color=" + color);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setFocusAppearance", "strokeWidth=" + strokeWidth + ";color=" + color);
}
}
@Override
- public void logTrace(long timestamp, String where, String callingParams, int processId,
- long threadId, int callingUid, Bundle callingStack) {
- if (mTrace.isA11yTracingEnabled()) {
+ public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+ int processId, long threadId, int callingUid, Bundle callingStack) {
+ if (mTrace.isA11yTracingEnabledForTypes(loggingTypes)) {
ArrayList<StackTraceElement> list =
(ArrayList<StackTraceElement>) callingStack.getSerializable(CALL_STACK);
- mTrace.logTrace(timestamp, where, callingParams, processId, threadId, callingUid,
- list.toArray(new StackTraceElement[list.size()]));
+ HashSet<String> ignoreList =
+ (HashSet<String>) callingStack.getSerializable(IGNORE_CALL_STACK);
+ mTrace.logTrace(timestamp, where, loggingTypes, callingParams, processId, threadId,
+ callingUid, list.toArray(new StackTraceElement[list.size()]), ignoreList);
}
}
+
+ protected boolean svcClientTracingEnabled() {
+ return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT);
+ }
+
+ protected void logTraceSvcClient(String methodName, String params) {
+ mTrace.logTrace(TRACE_SVC_CLIENT + "." + methodName,
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT, params);
+ }
+
+ protected boolean svcConnTracingEnabled() {
+ return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CONNECTION);
+ }
+
+ protected void logTraceSvcConn(String methodName, String params) {
+ mTrace.logTrace(TRACE_SVC_CONN + "." + methodName,
+ FLAGS_ACCESSIBILITY_SERVICE_CONNECTION, params);
+ }
+
+ protected boolean intConnTracingEnabled() {
+ return mTrace.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION);
+ }
+
+ protected void logTraceIntConn(String methodName, String params) {
+ mTrace.logTrace(LOG_TAG + "." + methodName,
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION, params);
+ }
+
+ protected boolean wmTracingEnabled() {
+ return mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL);
+ }
+
+ protected void logTraceWM(String methodName, String params) {
+ mTrace.logTrace(TRACE_WM + "." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 7403af7605bc..7d2b71f7852b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -19,7 +19,9 @@ package com.android.server.accessibility;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import android.accessibilityservice.AccessibilityTrace;
import android.annotation.MainThread;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Region;
import android.os.PowerManager;
@@ -43,7 +45,10 @@ import com.android.server.accessibility.magnification.WindowMagnificationGesture
import com.android.server.accessibility.magnification.WindowMagnificationPromptController;
import com.android.server.policy.WindowManagerPolicy;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.StringJoiner;
/**
* This class is an input filter for implementing accessibility features such
@@ -171,9 +176,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private int mEnabledFeatures;
- private EventStreamState mMouseStreamState;
+ private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0);
- private EventStreamState mTouchScreenStreamState;
+ private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
private EventStreamState mKeyboardStreamState;
@@ -211,10 +216,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
super.onUninstalled();
}
- void onDisplayChanged() {
+ void onDisplayAdded(@NonNull Display display) {
if (mInstalled) {
- disableFeatures();
- enableFeatures();
+ resetStreamStateForDisplay(display.getDisplayId());
+ enableFeaturesForDisplay(display);
+ }
+ }
+
+ void onDisplayRemoved(int displayId) {
+ if (mInstalled) {
+ disableFeaturesForDisplay(displayId);
+ resetStreamStateForDisplay(displayId);
}
}
@@ -224,7 +236,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
Slog.d(TAG, "Received event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
}
-
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(
+ AccessibilityTrace.FLAGS_INPUT_FILTER)) {
+ mAms.getTraceManager().logTrace(TAG + ".onInputEvent",
+ AccessibilityTrace.FLAGS_INPUT_FILTER,
+ "event=" + event + ";policyFlags=" + policyFlags);
+ }
if (mEventHandler.size() == 0) {
if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
super.onInputEvent(event, policyFlags);
@@ -237,16 +254,17 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
return;
}
- int eventSource = event.getSource();
+ final int eventSource = event.getSource();
+ final int displayId = event.getDisplayId();
if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
state.reset();
- clearEventsForAllEventHandlers(eventSource);
+ clearEventStreamHandler(displayId, eventSource);
super.onInputEvent(event, policyFlags);
return;
}
if (state.updateInputSource(event.getSource())) {
- clearEventsForAllEventHandlers(eventSource);
+ clearEventStreamHandler(displayId, eventSource);
}
if (!state.inputSourceValid()) {
@@ -275,35 +293,39 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
*/
private EventStreamState getEventStreamState(InputEvent event) {
if (event instanceof MotionEvent) {
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
- if (mTouchScreenStreamState == null) {
- mTouchScreenStreamState = new TouchScreenEventStreamState();
- }
- return mTouchScreenStreamState;
- }
- if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
- if (mMouseStreamState == null) {
- mMouseStreamState = new MouseEventStreamState();
- }
- return mMouseStreamState;
- }
+ final int displayId = event.getDisplayId();
+
+ if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+ EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
+ if (touchScreenStreamState == null) {
+ touchScreenStreamState = new TouchScreenEventStreamState();
+ mTouchScreenStreamStates.put(displayId, touchScreenStreamState);
+ }
+ return touchScreenStreamState;
+ }
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ EventStreamState mouseStreamState = mMouseStreamStates.get(displayId);
+ if (mouseStreamState == null) {
+ mouseStreamState = new MouseEventStreamState();
+ mMouseStreamStates.put(displayId, mouseStreamState);
+ }
+ return mouseStreamState;
+ }
} else if (event instanceof KeyEvent) {
- if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
- if (mKeyboardStreamState == null) {
- mKeyboardStreamState = new KeyboardEventStreamState();
- }
- return mKeyboardStreamState;
- }
+ if (event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
+ if (mKeyboardStreamState == null) {
+ mKeyboardStreamState = new KeyboardEventStreamState();
+ }
+ return mKeyboardStreamState;
+ }
}
return null;
}
- private void clearEventsForAllEventHandlers(int eventSource) {
- for (int i = 0; i < mEventHandler.size(); i++) {
- final EventStreamTransformation eventHandler = mEventHandler.valueAt(i);
- if (eventHandler != null) {
- eventHandler.clearEvents(eventSource);
- }
+ private void clearEventStreamHandler(int displayId, int eventSource) {
+ final EventStreamTransformation eventHandler = mEventHandler.get(displayId);
+ if (eventHandler != null) {
+ eventHandler.clearEvents(eventSource);
}
}
@@ -419,55 +441,69 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private void enableFeatures() {
if (DEBUG) Slog.i(TAG, "enableFeatures()");
- resetStreamState();
+ resetAllStreamState();
final ArrayList<Display> displaysList = mAms.getValidDisplayList();
- if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
- mAutoclickController = new AutoclickController(mContext, mUserId);
- addFirstEventHandlerForAllDisplays(displaysList, mAutoclickController);
+ for (int i = displaysList.size() - 1; i >= 0; i--) {
+ enableFeaturesForDisplay(displaysList.get(i));
}
+ enableDisplayIndependentFeatures();
+ }
- for (int i = displaysList.size() - 1; i >= 0; i--) {
- final int displayId = displaysList.get(i).getDisplayId();
- final Context displayContext = mContext.createDisplayContext(displaysList.get(i));
+ private void enableFeaturesForDisplay(Display display) {
+ if (DEBUG) {
+ Slog.i(TAG, "enableFeaturesForDisplay() : display Id = " + display.getDisplayId());
+ }
- if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
- TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
- if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
- explorer.setServiceHandlesDoubleTap(true);
- }
- if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
- explorer.setMultiFingerGesturesEnabled(true);
- }
- if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
- explorer.setTwoFingerPassthroughEnabled(true);
- }
- if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
- explorer.setSendMotionEventsEnabled(true);
- }
- addFirstEventHandler(displayId, explorer);
- mTouchExplorer.put(displayId, explorer);
- }
+ final Context displayContext = mContext.createDisplayContext(display);
+ final int displayId = display.getDisplayId();
- if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
- || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
- || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
- final MagnificationGestureHandler magnificationGestureHandler =
- createMagnificationGestureHandler(displayId,
- displayContext);
- addFirstEventHandler(displayId, magnificationGestureHandler);
- mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+ if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
+ if (mAutoclickController == null) {
+ mAutoclickController = new AutoclickController(
+ mContext, mUserId, mAms.getTraceManager());
}
+ addFirstEventHandler(displayId, mAutoclickController);
+ }
- if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
- MotionEventInjector injector = new MotionEventInjector(
- mContext.getMainLooper());
- addFirstEventHandler(displayId, injector);
- mMotionEventInjectors.put(displayId, injector);
+ if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ TouchExplorer explorer = new TouchExplorer(displayContext, mAms);
+ if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) {
+ explorer.setServiceHandlesDoubleTap(true);
+ }
+ if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) {
+ explorer.setMultiFingerGesturesEnabled(true);
+ }
+ if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) {
+ explorer.setTwoFingerPassthroughEnabled(true);
}
+ if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
+ explorer.setSendMotionEventsEnabled(true);
+ }
+ addFirstEventHandler(displayId, explorer);
+ mTouchExplorer.put(displayId, explorer);
+ }
+
+ if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
+ || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0)
+ || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
+ final MagnificationGestureHandler magnificationGestureHandler =
+ createMagnificationGestureHandler(displayId,
+ displayContext);
+ addFirstEventHandler(displayId, magnificationGestureHandler);
+ mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+ }
+
+ if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+ MotionEventInjector injector = new MotionEventInjector(
+ mContext.getMainLooper(), mAms.getTraceManager());
+ addFirstEventHandler(displayId, injector);
+ mMotionEventInjectors.put(displayId, injector);
}
+ }
+ private void enableDisplayIndependentFeatures() {
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
mAms.setMotionEventInjectors(mMotionEventInjectors);
}
@@ -500,55 +536,57 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mEventHandler.put(displayId, eventHandler);
}
- /**
- * Adds an event handler to the event handler chain for all displays. The handler is added at
- * the beginning of the chain.
- *
- * @param displayList The list of displays
- * @param handler The handler to be added to the event handlers list.
- */
- private void addFirstEventHandlerForAllDisplays(ArrayList<Display> displayList,
- EventStreamTransformation handler) {
- for (int i = 0; i < displayList.size(); i++) {
- final int displayId = displayList.get(i).getDisplayId();
- addFirstEventHandler(displayId, handler);
+ private void disableFeatures() {
+ final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
+ for (int i = displaysList.size() - 1; i >= 0; i--) {
+ disableFeaturesForDisplay(displaysList.get(i).getDisplayId());
}
+ mAms.setMotionEventInjectors(null);
+ disableDisplayIndependentFeatures();
+
+ resetAllStreamState();
}
- private void disableFeatures() {
- for (int i = mMotionEventInjectors.size() - 1; i >= 0; i--) {
- final MotionEventInjector injector = mMotionEventInjectors.valueAt(i);
- if (injector != null) {
- injector.onDestroy();
- }
+ private void disableFeaturesForDisplay(int displayId) {
+ if (DEBUG) {
+ Slog.i(TAG, "disableFeaturesForDisplay() : display Id = " + displayId);
}
- mAms.setMotionEventInjectors(null);
- mMotionEventInjectors.clear();
+
+ final MotionEventInjector injector = mMotionEventInjectors.get(displayId);
+ if (injector != null) {
+ injector.onDestroy();
+ mMotionEventInjectors.remove(displayId);
+ }
+
+ final TouchExplorer explorer = mTouchExplorer.get(displayId);
+ if (explorer != null) {
+ explorer.onDestroy();
+ mTouchExplorer.remove(displayId);
+ }
+
+ final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
+ if (handler != null) {
+ handler.onDestroy();
+ mMagnificationGestureHandler.remove(displayId);
+ }
+
+ final EventStreamTransformation eventStreamTransformation = mEventHandler.get(displayId);
+ if (eventStreamTransformation != null) {
+ mEventHandler.remove(displayId);
+ }
+ }
+
+ private void disableDisplayIndependentFeatures() {
if (mAutoclickController != null) {
mAutoclickController.onDestroy();
mAutoclickController = null;
}
- for (int i = mTouchExplorer.size() - 1; i >= 0; i--) {
- final TouchExplorer explorer = mTouchExplorer.valueAt(i);
- if (explorer != null) {
- explorer.onDestroy();
- }
- }
- mTouchExplorer.clear();
- for (int i = mMagnificationGestureHandler.size() - 1; i >= 0; i--) {
- final MagnificationGestureHandler handler = mMagnificationGestureHandler.valueAt(i);
- if (handler != null) {
- handler.onDestroy();
- }
- }
- mMagnificationGestureHandler.clear();
+
if (mKeyboardInterceptor != null) {
mKeyboardInterceptor.onDestroy();
mKeyboardInterceptor = null;
}
-
- mEventHandler.clear();
- resetStreamState();
}
private MagnificationGestureHandler createMagnificationGestureHandler(
@@ -563,32 +601,46 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
final Context uiContext = displayContext.createWindowContext(
TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext,
- mAms.getWindowMagnificationMgr(), mAms.getMagnificationController(),
- detectControlGestures, triggerable,
+ mAms.getWindowMagnificationMgr(), mAms.getTraceManager(),
+ mAms.getMagnificationController(), detectControlGestures, triggerable,
displayId);
} else {
final Context uiContext = displayContext.createWindowContext(
TYPE_MAGNIFICATION_OVERLAY, null /* options */);
magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext,
- mAms.getFullScreenMagnificationController(), mAms.getMagnificationController(),
- detectControlGestures, triggerable,
+ mAms.getFullScreenMagnificationController(), mAms.getTraceManager(),
+ mAms.getMagnificationController(), detectControlGestures, triggerable,
new WindowMagnificationPromptController(displayContext, mUserId), displayId);
}
return magnificationGestureHandler;
}
- void resetStreamState() {
- if (mTouchScreenStreamState != null) {
- mTouchScreenStreamState.reset();
- }
- if (mMouseStreamState != null) {
- mMouseStreamState.reset();
+ void resetAllStreamState() {
+ final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+
+ for (int i = displaysList.size() - 1; i >= 0; i--) {
+ resetStreamStateForDisplay(displaysList.get(i).getDisplayId());
}
+
if (mKeyboardStreamState != null) {
mKeyboardStreamState.reset();
}
}
+ void resetStreamStateForDisplay(int displayId) {
+ final EventStreamState touchScreenStreamState = mTouchScreenStreamStates.get(displayId);
+ if (touchScreenStreamState != null) {
+ touchScreenStreamState.reset();
+ mTouchScreenStreamStates.remove(displayId);
+ }
+
+ final EventStreamState mouseStreamState = mMouseStreamStates.get(displayId);
+ if (mouseStreamState != null) {
+ mouseStreamState.reset();
+ mMouseStreamStates.remove(displayId);
+ }
+ }
+
@Override
public void onDestroy() {
/* ignore */
@@ -839,4 +891,45 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
mTouchExplorer.get(displayId).setTouchExplorationPassthroughRegion(region);
}
}
+
+ /**
+ * Dumps all {@link AccessibilityInputFilter}s here.
+ */
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (mEventHandler == null) {
+ return;
+ }
+ pw.append("A11yInputFilter Info : ");
+ pw.println();
+
+ final ArrayList<Display> displaysList = mAms.getValidDisplayList();
+ for (int i = 0; i < displaysList.size(); i++) {
+ final int displayId = displaysList.get(i).getDisplayId();
+ EventStreamTransformation next = mEventHandler.get(displayId);
+ if (next != null) {
+ pw.append("Enabled features of Display [");
+ pw.append(Integer.toString(displayId));
+ pw.append("] = ");
+
+ final StringJoiner joiner = new StringJoiner(",", "[", "]");
+
+ while (next != null) {
+ if (next instanceof MagnificationGestureHandler) {
+ joiner.add("MagnificationGesture");
+ } else if (next instanceof KeyboardInterceptor) {
+ joiner.add("KeyboardInterceptor");
+ } else if (next instanceof TouchExplorer) {
+ joiner.add("TouchExplorer");
+ } else if (next instanceof AutoclickController) {
+ joiner.add("AutoclickController");
+ } else if (next instanceof MotionEventInjector) {
+ joiner.add("MotionEventInjector");
+ }
+ next = next.getNext();
+ }
+ pw.append(joiner.toString());
+ }
+ pw.println();
+ }
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f63198866b08..04ef10172c88 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,6 +16,15 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_FINGERPRINT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_PACKAGE_BROADCAST_RECEIVER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCAST_RECEIVER;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
@@ -289,8 +298,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext = context;
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
- mTraceManager = new AccessibilityTraceManager(
- mWindowManagerService.getAccessibilityController(), this);
+ mTraceManager = AccessibilityTraceManager.getInstance(
+ mWindowManagerService.getAccessibilityController(), this, mLock);
mMainHandler = new MainHandler(mContext.getMainLooper());
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManager = packageManager;
@@ -311,8 +320,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
- mTraceManager = new AccessibilityTraceManager(
- mWindowManagerService.getAccessibilityController(), this);
+ mTraceManager = AccessibilityTraceManager.getInstance(
+ mWindowManagerService.getAccessibilityController(), this, mLock);
mMainHandler = new MainHandler(mContext.getMainLooper());
mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManager = mContext.getPackageManager();
@@ -324,7 +333,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mSecurityPolicy = new AccessibilitySecurityPolicy(policyWarningUIController, mContext,
this);
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
- mWindowManagerService, this, mSecurityPolicy, this);
+ mWindowManagerService, this, mSecurityPolicy, this, mTraceManager);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
mMagnificationController = new MagnificationController(this, mLock, mContext);
init();
@@ -339,26 +348,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public int getCurrentUserIdLocked() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getCurrentUserIdLocked");
- }
return mCurrentUserId;
}
@Override
public boolean isAccessibilityButtonShown() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".isAccessibilityButtonShown");
- }
return mIsAccessibilityButtonShown;
}
@Override
public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(
- LOG_TAG + ".onServiceInfoChangedLocked", "userState=" + userState);
- }
mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId,
userState.mBoundServices);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
@@ -424,8 +423,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
PackageMonitor monitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged");
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
+ mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged",
+ FLAGS_PACKAGE_BROADCAST_RECEIVER);
}
synchronized (mLock) {
@@ -452,8 +452,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// mBindingServices in binderDied() during updating. Remove services from this
// package from mBindingServices, and then update the user state to re-bind new
// versions of them.
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished",
+ FLAGS_PACKAGE_BROADCAST_RECEIVER,
"packageName=" + packageName + ";uid=" + uid);
}
synchronized (mLock) {
@@ -485,8 +486,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void onPackageRemoved(String packageName, int uid) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved",
+ FLAGS_PACKAGE_BROADCAST_RECEIVER,
"packageName=" + packageName + ";uid=" + uid);
}
@@ -529,8 +531,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public boolean onHandleForceStop(Intent intent, String[] packages,
int uid, boolean doit) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) {
mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop",
+ FLAGS_PACKAGE_BROADCAST_RECEIVER,
"intent=" + intent + ";packages=" + packages + ";uid=" + uid
+ ";doit=" + doit);
}
@@ -580,8 +583,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".BR.onReceive",
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) {
+ mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER,
"context=" + context + ";intent=" + intent);
}
@@ -668,8 +671,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public long addClient(IAccessibilityManagerClient callback, int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".addClient",
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".addClient", FLAGS_ACCESSIBILITY_MANAGER,
"callback=" + callback + ";userId=" + userId);
}
@@ -739,8 +742,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent",
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent", FLAGS_ACCESSIBILITY_MANAGER,
"event=" + event + ";userId=" + userId);
}
boolean dispatchEvent = false;
@@ -803,6 +806,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
if (shouldComputeWindows) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+ mTraceManager.logTrace("WindowManagerInternal.computeWindowsForAccessibility",
+ FLAGS_WINDOW_MANAGER_INTERNAL, "display=" + displayId);
+ }
final WindowManagerInternal wm = LocalServices.getService(
WindowManagerInternal.class);
wm.computeWindowsForAccessibility(displayId);
@@ -835,9 +842,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public void registerSystemAction(RemoteAction action, int actionId) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerSystemAction",
- "action=" + action + ";actionId=" + actionId);
+ FLAGS_ACCESSIBILITY_MANAGER, "action=" + action + ";actionId=" + actionId);
}
mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
getSystemActionPerformer().registerSystemAction(actionId, action);
@@ -850,8 +857,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public void unregisterSystemAction(int actionId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction", "actionId=" + actionId);
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".unregisterSystemAction",
+ FLAGS_ACCESSIBILITY_MANAGER, "actionId=" + actionId);
}
mSecurityPolicy.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
getSystemActionPerformer().unregisterSystemAction(actionId);
@@ -867,9 +875,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getInstalledAccessibilityServiceList",
- "userId=" + userId);
+ FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId);
}
final int resolvedUserId;
@@ -903,8 +911,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getEnabledAccessibilityServiceList",
+ FLAGS_ACCESSIBILITY_MANAGER,
"feedbackType=" + feedbackType + ";userId=" + userId);
}
@@ -936,8 +945,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void interrupt(int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".interrupt", "userId=" + userId);
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".interrupt",
+ FLAGS_ACCESSIBILITY_MANAGER, "userId=" + userId);
}
List<IAccessibilityServiceClient> interfacesToInterrupt;
@@ -966,8 +976,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
try {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt");
+ if (mTraceManager.isA11yTracingEnabledForTypes(
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".IAccessibilityServiceClient.onInterrupt",
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT);
}
interfacesToInterrupt.get(i).onInterrupt();
} catch (RemoteException re) {
@@ -981,8 +993,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken,
IAccessibilityInteractionConnection connection, String packageName,
int userId) throws RemoteException {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".addAccessibilityInteractionConnection",
+ FLAGS_ACCESSIBILITY_MANAGER,
"windowToken=" + windowToken + "leashToken=" + leashToken + ";connection="
+ connection + "; packageName=" + packageName + ";userId=" + userId);
}
@@ -993,9 +1006,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void removeAccessibilityInteractionConnection(IWindow window) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".removeAccessibilityInteractionConnection",
- "window=" + window);
+ FLAGS_ACCESSIBILITY_MANAGER, "window=" + window);
}
mA11yWindowManager.removeAccessibilityInteractionConnection(window);
}
@@ -1003,9 +1016,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void setPictureInPictureActionReplacingConnection(
IAccessibilityInteractionConnection connection) throws RemoteException {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".setPictureInPictureActionReplacingConnection",
- "connection=" + connection);
+ FLAGS_ACCESSIBILITY_MANAGER, "connection=" + connection);
}
mSecurityPolicy.enforceCallingPermission(Manifest.permission.MODIFY_ACCESSIBILITY_DATA,
SET_PIP_ACTION_REPLACEMENT);
@@ -1017,10 +1030,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo,
int flags) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService", "owner=" + owner
- + ";serviceClient=" + serviceClient + ";accessibilityServiceInfo="
- + accessibilityServiceInfo + ";flags=" + flags);
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
+ FLAGS_ACCESSIBILITY_MANAGER,
+ "owner=" + owner + ";serviceClient=" + serviceClient
+ + ";accessibilityServiceInfo=" + accessibilityServiceInfo + ";flags=" + flags);
}
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
@@ -1037,9 +1051,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".unregisterUiTestAutomationService",
- "serviceClient=" + serviceClient);
+ FLAGS_ACCESSIBILITY_MANAGER, "serviceClient=" + serviceClient);
}
synchronized (mLock) {
mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
@@ -1049,15 +1063,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
ComponentName service, boolean touchExplorationEnabled) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(
LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved",
+ FLAGS_ACCESSIBILITY_MANAGER,
"service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled);
}
mSecurityPolicy.enforceCallingPermission(
Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+ mTraceManager.logTrace("WindowManagerInternal.isKeyguardLocked",
+ FLAGS_WINDOW_MANAGER_INTERNAL);
+ }
if (!mWindowManagerService.isKeyguardLocked()) {
return;
}
@@ -1083,9 +1102,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public IBinder getWindowToken(int windowId, int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getWindowToken",
- "windowId=" + windowId + ";userId=" + userId);
+ FLAGS_ACCESSIBILITY_MANAGER, "windowId=" + windowId + ";userId=" + userId);
}
mSecurityPolicy.enforceCallingPermission(
@@ -1127,8 +1146,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonClicked",
+ FLAGS_ACCESSIBILITY_MANAGER,
"displayId=" + displayId + ";targetName=" + targetName);
}
@@ -1157,9 +1177,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".notifyAccessibilityButtonVisibilityChanged",
- "shown=" + shown);
+ FLAGS_ACCESSIBILITY_MANAGER, "shown=" + shown);
}
mSecurityPolicy.enforceCallingOrSelfPermission(
@@ -1190,10 +1210,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public void onSystemActionsChanged() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".onSystemActionsChanged");
- }
-
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
notifySystemActionsChangedLocked(state);
@@ -1256,11 +1272,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public @Nullable MotionEventInjector getMotionEventInjectorForDisplayLocked(int displayId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getMotionEventInjectorForDisplayLocked",
- "displayId=" + displayId);
- }
-
final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
MotionEventInjector motionEventInjector = null;
while ((mMotionEventInjectors == null) && (SystemClock.uptimeMillis() < endMillis)) {
@@ -1323,6 +1334,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
synchronized (mLock) {
token = getWindowToken(windowId, mCurrentUserId);
}
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+ mTraceManager.logTrace("WindowManagerInternal.getWindowFrame",
+ FLAGS_WINDOW_MANAGER_INTERNAL, "token=" + token + ";outBounds=" + outBounds);
+ }
mWindowManagerService.getWindowFrame(token, outBounds);
if (!outBounds.isEmpty()) {
return true;
@@ -1471,7 +1486,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private int getClientStateLocked(AccessibilityUserState userState) {
return userState.getClientStateLocked(
mUiAutomationManager.isUiAutomationRunningLocked(),
- mTraceManager.isA11yTracingEnabled());
+ mTraceManager.getTraceStateForAccessibilityManagerClientState());
}
private InteractionBridge getInteractionBridge() {
@@ -1681,6 +1696,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private void updateRelevantEventsLocked(AccessibilityUserState userState) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".updateRelevantEventsLocked",
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+ }
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
int relevantEventTypes;
@@ -1830,12 +1849,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void persistComponentNamesToSettingLocked(String settingName,
Set<ComponentName> componentNames, int userId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".persistComponentNamesToSettingLocked",
- "settingName=" + settingName + ";componentNames=" + componentNames + ";userId="
- + userId);
- }
-
persistColonDelimitedSetToSettingLocked(settingName, userId, componentNames,
componentName -> componentName.flattenToShortString());
}
@@ -1960,7 +1973,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
- private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
final int clientState = getClientStateLocked(userState);
if (userState.getLastSentClientStateLocked() != clientState
&& (mGlobalClients.getRegisteredCallbackCount() > 0
@@ -1983,6 +1996,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void sendStateToClients(int clientState,
RemoteCallbackList<IAccessibilityManagerClient> clients) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".sendStateToClients",
+ FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "clientState=" + clientState);
+ }
clients.broadcast(ignoreRemoteException(
client -> client.setState(clientState)));
}
@@ -2003,6 +2020,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void notifyClientsOfServicesStateChange(
RemoteCallbackList<IAccessibilityManagerClient> clients, long uiTimeout) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".notifyClientsOfServicesStateChange",
+ FLAGS_ACCESSIBILITY_MANAGER_CLIENT, "uiTimeout=" + uiTimeout);
+ }
clients.broadcast(ignoreRemoteException(
client -> client.notifyServicesStateChanged(uiTimeout)));
}
@@ -2082,6 +2103,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
if (setInputFilter) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL
+ | FLAGS_INPUT_FILTER)) {
+ mTraceManager.logTrace("WindowManagerInternal.setInputFilter",
+ FLAGS_WINDOW_MANAGER_INTERNAL | FLAGS_INPUT_FILTER,
+ "inputFilter=" + inputFilter);
+ }
mWindowManagerService.setInputFilter(inputFilter);
}
}
@@ -2805,26 +2832,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@GuardedBy("mLock")
@Override
public MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getCompatibleMagnificationSpecLocked",
- "windowId=" + windowId);
- }
-
IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
mCurrentUserId, windowId);
if (windowToken != null) {
- return mWindowManagerService.getCompatibleMagnificationSpecForWindow(
- windowToken);
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
+ mTraceManager.logTrace(LOG_TAG + ".getCompatibleMagnificationSpecForWindow",
+ FLAGS_WINDOW_MANAGER_INTERNAL, "windowToken=" + windowToken);
+ }
+
+ return mWindowManagerService.getCompatibleMagnificationSpecForWindow(windowToken);
}
return null;
}
@Override
public KeyEventDispatcher getKeyEventDispatcher() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getKeyEventDispatcher");
- }
-
if (mKeyEventDispatcher == null) {
mKeyEventDispatcher = new KeyEventDispatcher(
mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock,
@@ -2837,13 +2859,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@SuppressWarnings("AndroidFrameworkPendingIntentMutability")
public PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent,
int flags) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getPendingIntentActivity",
- "context=" + context + ";requestCode=" + requestCode + ";intent=" + intent
- + ";flags=" + flags);
- }
-
-
return PendingIntent.getActivity(context, requestCode, intent, flags);
}
@@ -2858,9 +2873,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public void performAccessibilityShortcut(String targetName) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".performAccessibilityShortcut",
- "targetName=" + targetName);
+ FLAGS_ACCESSIBILITY_MANAGER, "targetName=" + targetName);
}
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
@@ -3048,9 +3063,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
- "shortcutType=" + shortcutType);
+ FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
}
if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
@@ -3122,11 +3137,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEventForCurrentUserLocked",
- "event=" + event);
- }
-
sendAccessibilityEventLocked(event, mCurrentUserId);
}
@@ -3148,8 +3158,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public boolean sendFingerprintGesture(int gestureKeyCode) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(
+ FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT)) {
mTraceManager.logTrace(LOG_TAG + ".sendFingerprintGesture",
+ FLAGS_ACCESSIBILITY_MANAGER | FLAGS_FINGERPRINT,
"gestureKeyCode=" + gestureKeyCode);
}
@@ -3174,9 +3186,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public int getAccessibilityWindowId(@Nullable IBinder windowToken) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityWindowId",
- "windowToken=" + windowToken);
+ FLAGS_ACCESSIBILITY_MANAGER, "windowToken=" + windowToken);
}
synchronized (mLock) {
@@ -3196,8 +3208,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
@Override
public long getRecommendedTimeoutMillis() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getRecommendedTimeoutMillis");
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(
+ LOG_TAG + ".getRecommendedTimeoutMillis", FLAGS_ACCESSIBILITY_MANAGER);
}
synchronized(mLock) {
@@ -3214,8 +3227,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void setWindowMagnificationConnection(
IWindowMagnificationConnection connection) throws RemoteException {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(
+ FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
mTraceManager.logTrace(LOG_TAG + ".setWindowMagnificationConnection",
+ FLAGS_ACCESSIBILITY_MANAGER | FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
"connection=" + connection);
}
@@ -3249,9 +3264,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) {
- if (mTraceManager.isA11yTracingEnabled()) {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".associateEmbeddedHierarchy",
- "host=" + host + ";embedded=" + embedded);
+ FLAGS_ACCESSIBILITY_MANAGER, "host=" + host + ";embedded=" + embedded);
}
synchronized (mLock) {
@@ -3261,8 +3276,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void disassociateEmbeddedHierarchy(@NonNull IBinder token) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy", "token=" + token);
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".disassociateEmbeddedHierarchy",
+ FLAGS_ACCESSIBILITY_MANAGER, "token=" + token);
}
synchronized (mLock) {
@@ -3274,7 +3290,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* Gets the stroke width of the focus rectangle.
* @return The stroke width.
*/
+ @Override
public int getFocusStrokeWidth() {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".getFocusStrokeWidth", FLAGS_ACCESSIBILITY_MANAGER);
+ }
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
@@ -3286,7 +3306,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* Gets the color of the focus rectangle.
* @return The color.
*/
+ @Override
public int getFocusColor() {
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
+ mTraceManager.logTrace(LOG_TAG + ".getFocusColor", FLAGS_ACCESSIBILITY_MANAGER);
+ }
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
@@ -3314,6 +3338,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
pw.println();
}
mA11yWindowManager.dump(fd, pw, args);
+ if (mHasInputFilter && mInputFilter != null) {
+ mInputFilter.dump(fd, pw, args);
+ }
pw.println("Global client list info:{");
mGlobalClients.dump(pw, " Client list ");
pw.println(" Registered clients:{");
@@ -3350,9 +3377,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public FullScreenMagnificationController getFullScreenMagnificationController() {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".getFullScreenMagnificationController");
- }
synchronized (mLock) {
return mMagnificationController.getFullScreenMagnificationController();
}
@@ -3360,11 +3384,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void onClientChangeLocked(boolean serviceInfoChanged) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".onClientChangeLocked",
- "serviceInfoChanged=" + serviceInfoChanged);
- }
-
AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
onUserStateChangedLocked(userState);
if (serviceInfoChanged) {
@@ -3569,7 +3588,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
synchronized (mLock) {
mDisplaysList.add(display);
if (mInputFilter != null) {
- mInputFilter.onDisplayChanged();
+ mInputFilter.onDisplayAdded(display);
}
AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
@@ -3591,7 +3610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
if (mInputFilter != null) {
- mInputFilter.onDisplayChanged();
+ mInputFilter.onDisplayRemoved(displayId);
}
AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
@@ -3891,11 +3910,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void setGestureDetectionPassthroughRegion(int displayId, Region region) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".setGestureDetectionPassthroughRegion",
- "displayId=" + displayId + ";region=" + region);
- }
-
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal,
@@ -3906,11 +3920,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
@Override
public void setTouchExplorationPassthroughRegion(int displayId, Region region) {
- if (mTraceManager.isA11yTracingEnabled()) {
- mTraceManager.logTrace(LOG_TAG + ".setTouchExplorationPassthroughRegion",
- "displayId=" + displayId + ";region=" + region);
- }
-
mMainHandler.sendMessage(
obtainMessage(
AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal,
@@ -3939,7 +3948,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (userState.mUserId != mCurrentUserId) {
return;
}
-
+ if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTraceManager.logTrace(LOG_TAG + ".updateFocusAppearanceDataLocked",
+ FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
+ }
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
client.mCallback.setFocusAppearance(userState.getFocusStrokeWidthLocked(),
@@ -3949,7 +3961,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
- AccessibilityTraceManager getTraceManager() {
+ public AccessibilityTraceManager getTraceManager() {
return mTraceManager;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 7d75b738d818..467cab5fec04 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -53,10 +54,7 @@ import java.util.Set;
*/
class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
private static final String LOG_TAG = "AccessibilityServiceConnection";
- private static final String TRACE_A11Y_SERVICE_CONNECTION =
- LOG_TAG + ".IAccessibilityServiceConnection";
- private static final String TRACE_A11Y_SERVICE_CLIENT =
- LOG_TAG + ".IAccessibilityServiceClient";
+
/*
Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
lists of bound and binding services. These are freed on user changes, but just in case it
@@ -137,8 +135,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public void disableSelf() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".disableSelf");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("disableSelf", "");
}
synchronized (mLock) {
AccessibilityUserState userState = mUserStateWeakReference.get();
@@ -218,9 +216,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
return;
}
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".init", this + ", " + mId + ", "
- + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("init",
+ this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
}
serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
} catch (RemoteException re) {
@@ -264,9 +262,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public boolean setSoftKeyboardShowMode(int showMode) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".setSoftKeyboardShowMode",
- "showMode=" + showMode);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("setSoftKeyboardShowMode", "showMode=" + showMode);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -280,8 +277,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public int getSoftKeyboardShowMode() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".getSoftKeyboardShowMode");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("getSoftKeyboardShowMode", "");
}
final AccessibilityUserState userState = mUserStateWeakReference.get();
return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
@@ -289,9 +286,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public boolean switchToInputMethod(String imeId) {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".switchToInputMethod",
- "imeId=" + imeId);
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("switchToInputMethod", "imeId=" + imeId);
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -311,8 +307,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public boolean isAccessibilityButtonAvailable() {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CONNECTION + ".isAccessibilityButtonAvailable");
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("isAccessibilityButtonAvailable", "");
}
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -373,9 +369,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
}
if (serviceInterface != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT
- + ".onFingerprintCapturingGesturesChanged", String.valueOf(active));
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient(
+ "onFingerprintCapturingGesturesChanged", String.valueOf(active));
}
mServiceInterface.onFingerprintCapturingGesturesChanged(active);
} catch (RemoteException e) {
@@ -394,9 +390,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
}
if (serviceInterface != null) {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onFingerprintGesture",
- String.valueOf(gesture));
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture));
}
mServiceInterface.onFingerprintGesture(gesture);
} catch (RemoteException e) {
@@ -410,15 +405,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (mSecurityPolicy.canPerformGestures(this)) {
MotionEventInjector motionEventInjector =
mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
+ if (wmTracingEnabled()) {
+ logTraceWM("isTouchOrFaketouchDevice", "");
+ }
if (motionEventInjector != null
&& mWindowManagerService.isTouchOrFaketouchDevice()) {
motionEventInjector.injectEvents(
gestureSteps.getList(), mServiceInterface, sequence, displayId);
} else {
try {
- if (mTrace.isA11yTracingEnabled()) {
- mTrace.logTrace(TRACE_A11Y_SERVICE_CLIENT + ".onPerformGestureResult",
- sequence + ", false");
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("onPerformGestureResult", sequence + ", false");
}
mServiceInterface.onPerformGestureResult(sequence, false);
} catch (RemoteException re) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index 6396960281b7..8cf5547b05ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -60,7 +60,7 @@ final class AccessibilityShellCommand extends ShellCommand {
}
case "start-trace":
case "stop-trace":
- return mService.getTraceManager().onShellCommand(cmd);
+ return mService.getTraceManager().onShellCommand(cmd, this);
}
return -1;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java
deleted file mode 100644
index 03914138cd42..000000000000
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTrace.java
+++ /dev/null
@@ -1,66 +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.accessibility;
-
-/**
- * Interface to log accessibility trace.
- */
-public interface AccessibilityTrace {
- /**
- * Whether the trace is enabled.
- */
- boolean isA11yTracingEnabled();
-
- /**
- * Start tracing.
- */
- void startTrace();
-
- /**
- * Stop tracing.
- */
- void stopTrace();
-
- /**
- * Log one trace entry.
- * @param where A string to identify this log entry, which can be used to filter/search
- * through the tracing file.
- */
- void logTrace(String where);
-
- /**
- * Log one trace entry.
- * @param where A string to identify this log entry, which can be used to filter/search
- * through the tracing file.
- * @param callingParams The parameters for the method to be logged.
- */
- void logTrace(String where, String callingParams);
-
- /**
- * Log one trace entry. Accessibility services using AccessibilityInteractionClient to
- * make screen content related requests use this API to log entry when receive callback.
- * @param timestamp The timestamp when a callback is received.
- * @param where A string to identify this log entry, which can be used to filter/search
- * through the tracing file.
- * @param callingParams The parameters for the callback.
- * @param processId The process id of the calling component.
- * @param threadId The threadId of the calling component.
- * @param callingUid The calling uid of the callback.
- * @param callStack The call stack of the callback.
- */
- void logTrace(long timestamp, String where, String callingParams, int processId,
- long threadId, int callingUid, StackTraceElement[] callStack);
-}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
index 6105e8a6724b..51e01ea58a35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityTraceManager.java
@@ -15,72 +15,197 @@
*/
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CLIENT;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_LOGGING_ALL;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_LOGGING_NONE;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED;
+
+import android.accessibilityservice.AccessibilityTrace;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.os.Binder;
+import android.os.ShellCommand;
import com.android.server.wm.WindowManagerInternal;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* Manager of accessibility trace.
*/
-class AccessibilityTraceManager implements AccessibilityTrace {
+public class AccessibilityTraceManager implements AccessibilityTrace {
private final WindowManagerInternal.AccessibilityControllerInternal mA11yController;
private final AccessibilityManagerService mService;
+ private final Object mA11yMSLock;
+
+ private long mEnabledLoggingFlags;
+
+ private static AccessibilityTraceManager sInstance = null;
+
+ @MainThread
+ static AccessibilityTraceManager getInstance(
+ @NonNull WindowManagerInternal.AccessibilityControllerInternal a11yController,
+ @NonNull AccessibilityManagerService service,
+ @NonNull Object lock) {
+ if (sInstance == null) {
+ sInstance = new AccessibilityTraceManager(a11yController, service, lock);
+ }
+ return sInstance;
+ }
- AccessibilityTraceManager(
+ private AccessibilityTraceManager(
@NonNull WindowManagerInternal.AccessibilityControllerInternal a11yController,
- @NonNull AccessibilityManagerService service) {
+ @NonNull AccessibilityManagerService service,
+ @NonNull Object lock) {
mA11yController = a11yController;
mService = service;
+ mA11yMSLock = lock;
+ mEnabledLoggingFlags = FLAGS_LOGGING_NONE;
}
@Override
public boolean isA11yTracingEnabled() {
- return mA11yController.isAccessibilityTracingEnabled();
+ synchronized (mA11yMSLock) {
+ return mEnabledLoggingFlags != FLAGS_LOGGING_NONE;
+ }
+ }
+
+ @Override
+ public boolean isA11yTracingEnabledForTypes(long typeIdFlags) {
+ synchronized (mA11yMSLock) {
+ return ((typeIdFlags & mEnabledLoggingFlags) != FLAGS_LOGGING_NONE);
+ }
+ }
+
+ @Override
+ public int getTraceStateForAccessibilityManagerClientState() {
+ int state = 0x0;
+ synchronized (mA11yMSLock) {
+ if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION)) {
+ state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_ENABLED;
+ }
+ if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION_CALLBACK)) {
+ state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CONNECTION_CB_ENABLED;
+ }
+ if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_INTERACTION_CLIENT)) {
+ state |= STATE_FLAG_TRACE_A11Y_INTERACTION_CLIENT_ENABLED;
+ }
+ if (isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE)) {
+ state |= STATE_FLAG_TRACE_A11Y_SERVICE_ENABLED;
+ }
+ }
+ return state;
}
@Override
- public void startTrace() {
- if (!mA11yController.isAccessibilityTracingEnabled()) {
- mA11yController.startTrace();
- mService.scheduleUpdateClientsIfNeeded(mService.getCurrentUserState());
+ public void startTrace(long loggingTypes) {
+ if (loggingTypes == FLAGS_LOGGING_NONE) {
+ // Ignore start none request
+ return;
+ }
+
+ synchronized (mA11yMSLock) {
+ long oldEnabled = mEnabledLoggingFlags;
+ mEnabledLoggingFlags = loggingTypes;
+
+ if (needToNotifyClients(oldEnabled)) {
+ mService.scheduleUpdateClientsIfNeededLocked(mService.getCurrentUserState());
+ }
}
+
+ mA11yController.startTrace(loggingTypes);
}
@Override
public void stopTrace() {
- if (mA11yController.isAccessibilityTracingEnabled()) {
+ boolean stop = false;
+ synchronized (mA11yMSLock) {
+ stop = isA11yTracingEnabled();
+
+ long oldEnabled = mEnabledLoggingFlags;
+ mEnabledLoggingFlags = FLAGS_LOGGING_NONE;
+
+ if (needToNotifyClients(oldEnabled)) {
+ mService.scheduleUpdateClientsIfNeededLocked(mService.getCurrentUserState());
+ }
+ }
+ if (stop) {
mA11yController.stopTrace();
- mService.scheduleUpdateClientsIfNeeded(mService.getCurrentUserState());
}
}
@Override
- public void logTrace(String where) {
- logTrace(where, "");
+ public void logTrace(String where, long loggingTypes) {
+ logTrace(where, loggingTypes, "");
}
@Override
- public void logTrace(String where, String callingParams) {
- mA11yController.logTrace(where, callingParams, "".getBytes(),
- Binder.getCallingUid(), Thread.currentThread().getStackTrace());
+ public void logTrace(String where, long loggingTypes, String callingParams) {
+ if (isA11yTracingEnabledForTypes(loggingTypes)) {
+ mA11yController.logTrace(where, loggingTypes, callingParams, "".getBytes(),
+ Binder.getCallingUid(), Thread.currentThread().getStackTrace(),
+ new HashSet<String>(Arrays.asList("logTrace")));
+ }
}
@Override
- public void logTrace(long timestamp, String where, String callingParams, int processId,
- long threadId, int callingUid, StackTraceElement[] callStack) {
- if (mA11yController.isAccessibilityTracingEnabled()) {
- mA11yController.logTrace(where, callingParams, "".getBytes(), callingUid, callStack,
- timestamp, processId, threadId);
+ public void logTrace(long timestamp, String where, long loggingTypes, String callingParams,
+ int processId, long threadId, int callingUid, StackTraceElement[] callStack,
+ Set<String> ignoreElementList) {
+ if (isA11yTracingEnabledForTypes(loggingTypes)) {
+ mA11yController.logTrace(where, loggingTypes, callingParams, "".getBytes(), callingUid,
+ callStack, timestamp, processId, threadId,
+ ((ignoreElementList == null) ? new HashSet<String>() : ignoreElementList));
}
}
- int onShellCommand(String cmd) {
+ private boolean needToNotifyClients(long otherTypesEnabled) {
+ return (mEnabledLoggingFlags & FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES)
+ != (otherTypesEnabled & FLAGS_ACCESSIBILITY_MANAGER_CLIENT_STATES);
+ }
+
+ int onShellCommand(String cmd, ShellCommand shell) {
switch (cmd) {
case "start-trace": {
- startTrace();
+ String opt = shell.getNextOption();
+ if (opt == null) {
+ startTrace(FLAGS_LOGGING_ALL);
+ return 0;
+ }
+ List<String> types = new ArrayList<String>();
+ while (opt != null) {
+ switch (opt) {
+ case "-t": {
+ String type = shell.getNextArg();
+ while (type != null) {
+ types.add(type);
+ type = shell.getNextArg();
+ }
+ break;
+ }
+ default: {
+ shell.getErrPrintWriter().println(
+ "Error: option not recognized " + opt);
+ stopTrace();
+ return -1;
+ }
+ }
+ opt = shell.getNextOption();
+ }
+ long enabledTypes = AccessibilityTrace.getLoggingFlagsFromNames(types);
+ startTrace(enabledTypes);
return 0;
}
case "stop-trace": {
@@ -92,8 +217,29 @@ class AccessibilityTraceManager implements AccessibilityTrace {
}
void onHelp(PrintWriter pw) {
- pw.println(" start-trace");
- pw.println(" Start the debug tracing.");
+ pw.println(" start-trace [-t LOGGING_TYPE [LOGGING_TYPE...]]");
+ pw.println(" Start the debug tracing. If no option is present, full trace will be");
+ pw.println(" generated. Options are:");
+ pw.println(" -t: Only generate tracing for the logging type(s) specified here.");
+ pw.println(" LOGGING_TYPE can be any one of below:");
+ pw.println(" IAccessibilityServiceConnection");
+ pw.println(" IAccessibilityServiceClient");
+ pw.println(" IAccessibilityManager");
+ pw.println(" IAccessibilityManagerClient");
+ pw.println(" IAccessibilityInteractionConnection");
+ pw.println(" IAccessibilityInteractionConnectionCallback");
+ pw.println(" IRemoteMagnificationAnimationCallback");
+ pw.println(" IWindowMagnificationConnection");
+ pw.println(" IWindowMagnificationConnectionCallback");
+ pw.println(" WindowManagerInternal");
+ pw.println(" WindowsForAccessibilityCallback");
+ pw.println(" MagnificationCallbacks");
+ pw.println(" InputFilter");
+ pw.println(" Gesture");
+ pw.println(" AccessibilityService");
+ pw.println(" PMBroadcastReceiver");
+ pw.println(" UserBroadcastReceiver");
+ pw.println(" FingerprintGesture");
pw.println(" stop-trace");
pw.println(" Stop the debug tracing.");
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0fde0de59c07..c70bf73fc7f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -392,7 +392,7 @@ class AccessibilityUserState {
return mBoundServices;
}
- int getClientStateLocked(boolean isUiAutomationRunning, boolean isTracingEnabled) {
+ int getClientStateLocked(boolean isUiAutomationRunning, int traceClientState) {
int clientState = 0;
final boolean a11yEnabled = isUiAutomationRunning
|| isHandlingAccessibilityEventsLocked();
@@ -408,9 +408,9 @@ class AccessibilityUserState {
if (mIsTextHighContrastEnabled) {
clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
}
- if (isTracingEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_TRACING_ENABLED;
- }
+
+ clientState |= traceClientState;
+
return clientState;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index ff794691d2b4..244f35700d8b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
@@ -69,6 +71,7 @@ public class AccessibilityWindowManager {
private final AccessibilityEventSender mAccessibilityEventSender;
private final AccessibilitySecurityPolicy mSecurityPolicy;
private final AccessibilityUserManager mAccessibilityUserManager;
+ private final AccessibilityTraceManager mTraceManager;
// Connections and window tokens for cross-user windows
private final SparseArray<RemoteAccessibilityConnection>
@@ -151,6 +154,10 @@ public class AccessibilityWindowManager {
// In some cases, onWindowsForAccessibilityChanged will be called immediately in
// setWindowsForAccessibilityCallback. We'll lost windows if flag is false.
mTrackingWindows = true;
+ if (traceWMEnabled()) {
+ logTraceWM("setWindowsForAccessibilityCallback",
+ "displayId=" + mDisplayId + ";callback=" + this);
+ }
result = mWindowManagerInternal.setWindowsForAccessibilityCallback(
mDisplayId, this);
if (!result) {
@@ -167,6 +174,10 @@ public class AccessibilityWindowManager {
*/
void stopTrackingWindowsLocked() {
if (mTrackingWindows) {
+ if (traceWMEnabled()) {
+ logTraceWM("setWindowsForAccessibilityCallback",
+ "displayId=" + mDisplayId + ";callback=null");
+ }
mWindowManagerInternal.setWindowsForAccessibilityCallback(
mDisplayId, null);
mTrackingWindows = false;
@@ -373,6 +384,20 @@ public class AccessibilityWindowManager {
}
}
+ /**
+ * Called when the display is reparented and becomes an embedded
+ * display.
+ *
+ * @param embeddedDisplayId The embedded display Id.
+ */
+ @Override
+ public void onDisplayReparented(int embeddedDisplayId) {
+ // Removes the un-used window observer for the embedded display.
+ synchronized (mLock) {
+ mDisplayWindowsObservers.remove(embeddedDisplayId);
+ }
+ }
+
private boolean shouldUpdateWindowsLocked(boolean forceSend,
@NonNull List<WindowInfo> windows) {
if (forceSend) {
@@ -474,6 +499,9 @@ public class AccessibilityWindowManager {
if (oldWindow.displayId != newWindow.displayId) {
return true;
}
+ if (oldWindow.taskId != newWindow.taskId) {
+ return true;
+ }
return false;
}
@@ -674,6 +702,7 @@ public class AccessibilityWindowManager {
reportedWindow.setAnchorId(window.accessibilityIdOfAnchor);
reportedWindow.setPictureInPicture(window.inPictureInPicture);
reportedWindow.setDisplayId(window.displayId);
+ reportedWindow.setTaskId(window.taskId);
final int parentId = findWindowIdLocked(userId, window.parentToken);
if (parentId >= 0) {
@@ -844,19 +873,21 @@ public class AccessibilityWindowManager {
}
/**
- * Constructor for AccessibilityManagerService.
+ * Constructor for AccessibilityWindowManager.
*/
public AccessibilityWindowManager(@NonNull Object lock, @NonNull Handler handler,
@NonNull WindowManagerInternal windowManagerInternal,
@NonNull AccessibilityEventSender accessibilityEventSender,
@NonNull AccessibilitySecurityPolicy securityPolicy,
- @NonNull AccessibilityUserManager accessibilityUserManager) {
+ @NonNull AccessibilityUserManager accessibilityUserManager,
+ @NonNull AccessibilityTraceManager traceManager) {
mLock = lock;
mHandler = handler;
mWindowManagerInternal = windowManagerInternal;
mAccessibilityEventSender = accessibilityEventSender;
mSecurityPolicy = securityPolicy;
mAccessibilityUserManager = accessibilityUserManager;
+ mTraceManager = traceManager;
}
/**
@@ -957,6 +988,9 @@ public class AccessibilityWindowManager {
final int windowId;
boolean shouldComputeWindows = false;
final IBinder token = window.asBinder();
+ if (traceWMEnabled()) {
+ logTraceWM("getDisplayIdForWindow", "token=" + token);
+ }
final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token);
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
@@ -1003,9 +1037,15 @@ public class AccessibilityWindowManager {
registerIdLocked(leashToken, windowId);
}
if (shouldComputeWindows) {
+ if (traceWMEnabled()) {
+ logTraceWM("computeWindowsForAccessibility", "displayId=" + displayId);
+ }
mWindowManagerInternal.computeWindowsForAccessibility(displayId);
}
-
+ if (traceWMEnabled()) {
+ logTraceWM("setAccessibilityIdToSurfaceMetadata",
+ "token=" + token + ";windowId=" + windowId);
+ }
mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId);
return windowId;
}
@@ -1139,6 +1179,10 @@ public class AccessibilityWindowManager {
mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
if (binder != null) {
+ if (traceWMEnabled()) {
+ logTraceWM("setAccessibilityIdToSurfaceMetadata", "token=" + binder
+ + ";windowId=AccessibilityWindowInfo.UNDEFINED_WINDOW_ID");
+ }
mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(
binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
}
@@ -1169,6 +1213,9 @@ public class AccessibilityWindowManager {
* @return The userId
*/
public int getWindowOwnerUserId(@NonNull IBinder windowToken) {
+ if (traceWMEnabled()) {
+ logTraceWM("getWindowOwnerUserId", "token=" + windowToken);
+ }
return mWindowManagerInternal.getWindowOwnerUserId(windowToken);
}
@@ -1547,6 +1594,10 @@ public class AccessibilityWindowManager {
for (int i = 0; i < connectionList.size(); i++) {
final RemoteAccessibilityConnection connection = connectionList.get(i);
if (connection != null) {
+ if (traceIntConnEnabled()) {
+ logTraceIntConn("notifyOutsideTouch");
+ }
+
try {
connection.getRemote().notifyOutsideTouch();
} catch (RemoteException re) {
@@ -1567,6 +1618,9 @@ public class AccessibilityWindowManager {
*/
public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+ if (traceWMEnabled()) {
+ logTraceWM("getDisplayIdForWindow", "token=" + windowToken);
+ }
final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
return displayId;
}
@@ -1595,6 +1649,9 @@ public class AccessibilityWindowManager {
* @return The input focused windowId, or -1 if not found
*/
private int findFocusedWindowId(int userId) {
+ if (traceWMEnabled()) {
+ logTraceWM("getFocusedWindowToken", "");
+ }
final IBinder token = mWindowManagerInternal.getFocusedWindowToken();
synchronized (mLock) {
return findWindowIdLocked(userId, token);
@@ -1644,6 +1701,9 @@ public class AccessibilityWindowManager {
return;
}
}
+ if (traceIntConnEnabled()) {
+ logTraceIntConn("notifyOutsideTouch");
+ }
try {
connection.getRemote().clearAccessibilityFocus();
} catch (RemoteException re) {
@@ -1666,6 +1726,25 @@ public class AccessibilityWindowManager {
return null;
}
+ private boolean traceWMEnabled() {
+ return mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL);
+ }
+
+ private void logTraceWM(String methodName, String params) {
+ mTraceManager.logTrace("WindowManagerInternal." + methodName,
+ FLAGS_WINDOW_MANAGER_INTERNAL, params);
+ }
+
+ private boolean traceIntConnEnabled() {
+ return mTraceManager.isA11yTracingEnabledForTypes(
+ FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION);
+ }
+
+ private void logTraceIntConn(String methodName) {
+ mTraceManager.logTrace(
+ LOG_TAG + "." + methodName, FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION);
+ }
+
/**
* Associate the token of the embedded view hierarchy to the host view hierarchy.
*
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index f5b0eb1eb5b6..95f3560dbbb8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
@@ -56,6 +57,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
private static final String LOG_TAG = AutoclickController.class.getSimpleName();
+ private final AccessibilityTraceManager mTrace;
private final Context mContext;
private final int mUserId;
@@ -63,13 +65,18 @@ public class AutoclickController extends BaseEventStreamTransformation {
private ClickScheduler mClickScheduler;
private ClickDelayObserver mClickDelayObserver;
- public AutoclickController(Context context, int userId) {
+ public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
+ mTrace = trace;
mContext = context;
mUserId = userId;
}
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) {
+ mTrace.logTrace(LOG_TAG + ".onMotionEvent", AccessibilityTrace.FLAGS_INPUT_FILTER,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (mClickScheduler == null) {
Handler handler = new Handler(mContext.getMainLooper());
@@ -89,6 +96,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
@Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (mTrace.isA11yTracingEnabledForTypes(AccessibilityTrace.FLAGS_INPUT_FILTER)) {
+ mTrace.logTrace(LOG_TAG + ".onKeyEvent", AccessibilityTrace.FLAGS_INPUT_FILTER,
+ "event=" + event + ";policyFlags=" + policyFlags);
+ }
if (mClickScheduler != null) {
if (KeyEvent.isModifierKey(event.getKeyCode())) {
mClickScheduler.updateMetaState(event.getMetaState());
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
index bc379c204d44..b8250c028f62 100644
--- a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
+
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
@@ -64,6 +66,10 @@ public class KeyboardInterceptor extends BaseEventStreamTransformation implement
@Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(FLAGS_INPUT_FILTER)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onKeyEvent",
+ FLAGS_INPUT_FILTER, "event=" + event + ";policyFlags=" + policyFlags);
+ }
/*
* Certain keys have system-level behavior that affects accessibility services.
* Let that behavior settle before handling the keys
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 2673cd1ac3b8..5cbd1a208ce1 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.GestureDescription.GestureStep;
import android.accessibilityservice.GestureDescription.TouchPoint;
@@ -68,6 +69,7 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
private final Handler mHandler;
private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
+ private final AccessibilityTraceManager mTrace;
private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
private IntArray mSequencesInProgress = new IntArray(5);
private boolean mIsDestroyed = false;
@@ -80,15 +82,17 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
/**
* @param looper A looper on the main thread to use for dispatching new events
*/
- public MotionEventInjector(Looper looper) {
+ public MotionEventInjector(Looper looper, AccessibilityTraceManager trace) {
mHandler = new Handler(looper, this);
+ mTrace = trace;
}
/**
* @param handler A handler to post messages. Exposes internal state for testing only.
*/
- public MotionEventInjector(Handler handler) {
+ public MotionEventInjector(Handler handler, AccessibilityTraceManager trace) {
mHandler = handler;
+ mTrace = trace;
}
/**
@@ -112,6 +116,12 @@ public class MotionEventInjector extends BaseEventStreamTransformation implement
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) {
+ mTrace.logTrace(LOG_TAG + ".onMotionEvent",
+ AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
// MotionEventInjector would cancel any injected gesture when any MotionEvent arrives.
// For user using an external device to control the pointer movement, it's almost
// impossible to perform the gestures. Any slightly unintended movement results in the
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index eaf269415fdc..6744ea8e26a5 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -288,8 +288,6 @@ public class SystemActionPerformer {
showGlobalActions();
return true;
}
- case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN:
- return toggleSplitScreen();
case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN:
return lockScreen();
case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT:
@@ -369,21 +367,6 @@ public class SystemActionPerformer {
mWindowManagerService.showGlobalActions();
}
- private boolean toggleSplitScreen() {
- final long token = Binder.clearCallingIdentity();
- try {
- StatusBarManagerInternal statusBarService = LocalServices.getService(
- StatusBarManagerInternal.class);
- if (statusBarService == null) {
- return false;
- }
- statusBarService.toggleSplitScreen();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return true;
- }
-
private boolean lockScreen() {
mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 9547280018e4..6cd23fcfd0fb 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.Nullable;
import android.app.UiAutomation;
@@ -269,6 +270,14 @@ class UiAutomationManager {
// If the serviceInterface is null, the UiAutomation has been shut down on
// another thread.
if (serviceInterface != null) {
+ 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));
}
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 d7bc04091181..74f0bcb4245c 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.gestures;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_GESTURE;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_INPUT_FILTER;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
@@ -82,6 +84,7 @@ public class TouchExplorer extends BaseEventStreamTransformation
implements GestureManifold.Listener {
static final boolean DEBUG = false;
+ private static final long LOGGING_FLAGS = FLAGS_GESTURE | FLAGS_INPUT_FILTER;
// Tag for logging received events.
private static final String LOG_TAG = "TouchExplorer";
@@ -254,6 +257,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onMotionEvent", LOGGING_FLAGS,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
super.onMotionEvent(event, rawEvent, policyFlags);
return;
@@ -308,6 +315,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onAccessibilityEvent",
+ LOGGING_FLAGS, "event=" + event);
+ }
final int eventType = event.getEventType();
if (eventType == TYPE_VIEW_HOVER_EXIT) {
@@ -346,6 +357,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTapAndHold", LOGGING_FLAGS,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
if (isSendMotionEventsEnabled()) {
@@ -362,6 +377,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onDoubleTap(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onDoubleTap", LOGGING_FLAGS,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
mAms.onTouchInteractionEnd();
// Remove pending event deliveries.
mSendHoverEnterAndMoveDelayed.cancel();
@@ -394,6 +413,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onGestureStarted() {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureStarted", LOGGING_FLAGS);
+ }
// We have to perform gesture detection, so
// clear the current state and try to detect.
mSendHoverEnterAndMoveDelayed.cancel();
@@ -407,6 +429,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCompleted",
+ LOGGING_FLAGS, "event=" + gestureEvent);
+ }
endGestureDetection(true);
mSendTouchInteractionEndDelayed.cancel();
dispatchGesture(gestureEvent);
@@ -415,6 +441,10 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public boolean onGestureCancelled(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (mAms.getTraceManager().isA11yTracingEnabledForTypes(LOGGING_FLAGS)) {
+ mAms.getTraceManager().logTrace(LOG_TAG + ".onGestureCancelled", LOGGING_FLAGS,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
if (mState.isGestureDetecting()) {
endGestureDetection(event.getActionMasked() == ACTION_UP);
return true;
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 1f66bfdb20c1..718da9e390ab 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.magnification;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
+
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -46,6 +48,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.wm.WindowManagerInternal;
import java.util.Locale;
@@ -135,6 +138,10 @@ public class FullScreenMagnificationController {
*/
@GuardedBy("mLock")
boolean register() {
+ if (traceEnabled()) {
+ logTrace("setMagnificationCallbacks",
+ "displayID=" + mDisplayId + ";callback=" + this);
+ }
mRegistered = mControllerCtx.getWindowManager().setMagnificationCallbacks(
mDisplayId, this);
if (!mRegistered) {
@@ -142,6 +149,10 @@ public class FullScreenMagnificationController {
return false;
}
mSpecAnimationBridge.setEnabled(true);
+ if (traceEnabled()) {
+ logTrace("getMagnificationRegion",
+ "displayID=" + mDisplayId + ";region=" + mMagnificationRegion);
+ }
// Obtain initial state.
mControllerCtx.getWindowManager().getMagnificationRegion(
mDisplayId, mMagnificationRegion);
@@ -162,6 +173,10 @@ public class FullScreenMagnificationController {
void unregister(boolean delete) {
if (mRegistered) {
mSpecAnimationBridge.setEnabled(false);
+ if (traceEnabled()) {
+ logTrace("setMagnificationCallbacks",
+ "displayID=" + mDisplayId + ";callback=null");
+ }
mControllerCtx.getWindowManager().setMagnificationCallbacks(
mDisplayId, null);
mMagnificationRegion.setEmpty();
@@ -268,7 +283,7 @@ public class FullScreenMagnificationController {
}
@Override
- public void onRotationChanged(int rotation) {
+ public void onDisplaySizeChanged() {
// Treat as context change and reset
final Message m = PooledLambda.obtainMessage(
FullScreenMagnificationController::resetIfNeeded,
@@ -431,6 +446,10 @@ public class FullScreenMagnificationController {
void setForceShowMagnifiableBounds(boolean show) {
if (mRegistered) {
mForceShowMagnifiableBounds = show;
+ if (traceEnabled()) {
+ logTrace("setForceShowMagnifiableBounds",
+ "displayID=" + mDisplayId + ";show=" + show);
+ }
mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
mDisplayId, show);
}
@@ -1255,6 +1274,16 @@ public class FullScreenMagnificationController {
}
}
+ private boolean traceEnabled() {
+ return mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MANAGER_INTERNAL);
+ }
+
+ private void logTrace(String methodName, String params) {
+ mControllerCtx.getTraceManager().logTrace(
+ "WindowManagerInternal." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params);
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -1320,6 +1349,13 @@ public class FullScreenMagnificationController {
mEnabled = enabled;
if (!mEnabled) {
mSentMagnificationSpec.clear();
+ if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MANAGER_INTERNAL)) {
+ mControllerCtx.getTraceManager().logTrace(
+ "WindowManagerInternal.setMagnificationSpec",
+ FLAGS_WINDOW_MANAGER_INTERNAL,
+ "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec);
+ }
mControllerCtx.getWindowManager().setMagnificationSpec(
mDisplayId, mSentMagnificationSpec);
}
@@ -1367,6 +1403,13 @@ public class FullScreenMagnificationController {
}
mSentMagnificationSpec.setTo(spec);
+ if (mControllerCtx.getTraceManager().isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MANAGER_INTERNAL)) {
+ mControllerCtx.getTraceManager().logTrace(
+ "WindowManagerInternal.setMagnificationSpec",
+ FLAGS_WINDOW_MANAGER_INTERNAL,
+ "displayID=" + mDisplayId + ";spec=" + mSentMagnificationSpec);
+ }
mControllerCtx.getWindowManager().setMagnificationSpec(
mDisplayId, mSentMagnificationSpec);
}
@@ -1455,6 +1498,7 @@ public class FullScreenMagnificationController {
public static class ControllerContext {
private final Context mContext;
private final AccessibilityManagerService mAms;
+ private final AccessibilityTraceManager mTrace;
private final WindowManagerInternal mWindowManager;
private final Handler mHandler;
private final Long mAnimationDuration;
@@ -1469,6 +1513,7 @@ public class FullScreenMagnificationController {
long animationDuration) {
mContext = context;
mAms = ams;
+ mTrace = ams.getTraceManager();
mWindowManager = windowManager;
mHandler = handler;
mAnimationDuration = animationDuration;
@@ -1491,6 +1536,14 @@ public class FullScreenMagnificationController {
}
/**
+ * @return AccessibilityTraceManager
+ */
+ @NonNull
+ public AccessibilityTraceManager getTraceManager() {
+ return mTrace;
+ }
+
+ /**
* @return WindowManagerInternal
*/
@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 f7d1b9a311ba..c3d8d4c2c96a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -61,6 +61,7 @@ import android.view.ViewConfiguration;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.gestures.GestureUtils;
/**
@@ -142,12 +143,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
public FullScreenMagnificationGestureHandler(@UiContext Context context,
FullScreenMagnificationController fullScreenMagnificationController,
+ AccessibilityTraceManager trace,
Callback callback,
boolean detectTripleTap,
boolean detectShortcutTrigger,
@NonNull WindowMagnificationPromptController promptController,
int displayId) {
- super(displayId, detectTripleTap, detectShortcutTrigger, callback);
+ super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
Log.i(mLogTag,
"FullScreenMagnificationGestureHandler(detectTripleTap = " + detectTripleTap
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 f9aecd739d33..8aacafbd05c7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -411,8 +411,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb
synchronized (mLock) {
if (mWindowMagnificationMgr == null) {
mWindowMagnificationMgr = new WindowMagnificationManager(mContext,
- mAms.getCurrentUserIdLocked(),
- this);
+ mAms.getCurrentUserIdLocked(), this, mAms.getTraceManager());
}
return mWindowMagnificationMgr;
}
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 bbe40b6faf74..19b339645557 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -20,11 +20,13 @@ import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_UP;
+import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.BaseEventStreamTransformation;
import java.util.ArrayDeque;
@@ -99,14 +101,17 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo
void onTripleTapped(int displayId, int mode);
}
+ private final AccessibilityTraceManager mTrace;
protected final Callback mCallback;
protected MagnificationGestureHandler(int displayId, boolean detectTripleTap,
boolean detectShortcutTrigger,
+ AccessibilityTraceManager trace,
@NonNull Callback callback) {
mDisplayId = displayId;
mDetectTripleTap = detectTripleTap;
mDetectShortcutTrigger = detectShortcutTrigger;
+ mTrace = trace;
mCallback = callback;
mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
@@ -118,6 +123,12 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo
if (DEBUG_ALL) {
Slog.i(mLogTag, "onMotionEvent(" + event + ")");
}
+ if (mTrace.isA11yTracingEnabledForTypes(
+ AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) {
+ mTrace.logTrace("MagnificationGestureHandler.onMotionEvent",
+ AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE,
+ "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
+ }
if (DEBUG_EVENT_STREAM) {
storeEventInto(mDebugInputEventHistory, event);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
index 993027d1ca3c..527742536480 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
@@ -16,6 +16,9 @@
package com.android.server.accessibility.magnification;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
import static android.os.IBinder.DeathRecipient;
import android.annotation.NonNull;
@@ -27,6 +30,8 @@ import android.view.accessibility.IWindowMagnificationConnection;
import android.view.accessibility.IWindowMagnificationConnectionCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.server.accessibility.AccessibilityTraceManager;
+
/**
* A wrapper of {@link IWindowMagnificationConnection}.
*/
@@ -36,9 +41,12 @@ class WindowMagnificationConnectionWrapper {
private static final String TAG = "WindowMagnificationConnectionWrapper";
private final @NonNull IWindowMagnificationConnection mConnection;
+ private final @NonNull AccessibilityTraceManager mTrace;
- WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection) {
+ WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection,
+ @NonNull AccessibilityTraceManager trace) {
mConnection = connection;
+ mTrace = trace;
}
//Should not use this instance anymore after calling it.
@@ -52,9 +60,15 @@ class WindowMagnificationConnectionWrapper {
boolean enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
@Nullable MagnificationAnimationCallback callback) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".enableWindowMagnification",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+ "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
+ + ";centerY=" + centerY + ";callback=" + callback);
+ }
try {
mConnection.enableWindowMagnification(displayId, scale, centerX, centerY,
- transformToRemoteCallback(callback));
+ transformToRemoteCallback(callback, mTrace));
} catch (RemoteException e) {
if (DBG) {
Slog.e(TAG, "Error calling enableWindowMagnification()", e);
@@ -65,6 +79,10 @@ class WindowMagnificationConnectionWrapper {
}
boolean setScale(int displayId, float scale) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".setScale", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+ "displayId=" + displayId + ";scale=" + scale);
+ }
try {
mConnection.setScale(displayId, scale);
} catch (RemoteException e) {
@@ -78,8 +96,14 @@ class WindowMagnificationConnectionWrapper {
boolean disableWindowMagnification(int displayId,
@Nullable MagnificationAnimationCallback callback) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".disableWindowMagnification",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+ "displayId=" + displayId + ";callback=" + callback);
+ }
try {
- mConnection.disableWindowMagnification(displayId, transformToRemoteCallback(callback));
+ mConnection.disableWindowMagnification(displayId,
+ transformToRemoteCallback(callback, mTrace));
} catch (RemoteException e) {
if (DBG) {
Slog.e(TAG, "Error calling disableWindowMagnification()", e);
@@ -90,6 +114,10 @@ class WindowMagnificationConnectionWrapper {
}
boolean moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".moveWindowMagnifier", FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+ "displayId=" + displayId + ";offsetX=" + offsetX + ";offsetY=" + offsetY);
+ }
try {
mConnection.moveWindowMagnifier(displayId, offsetX, offsetY);
} catch (RemoteException e) {
@@ -102,6 +130,11 @@ class WindowMagnificationConnectionWrapper {
}
boolean showMagnificationButton(int displayId, int magnificationMode) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".showMagnificationButton",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION,
+ "displayId=" + displayId + ";mode=" + magnificationMode);
+ }
try {
mConnection.showMagnificationButton(displayId, magnificationMode);
} catch (RemoteException e) {
@@ -114,6 +147,10 @@ class WindowMagnificationConnectionWrapper {
}
boolean removeMagnificationButton(int displayId) {
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".removeMagnificationButton",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "displayId=" + displayId);
+ }
try {
mConnection.removeMagnificationButton(displayId);
} catch (RemoteException e) {
@@ -126,6 +163,14 @@ class WindowMagnificationConnectionWrapper {
}
boolean setConnectionCallback(IWindowMagnificationConnectionCallback connectionCallback) {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION
+ | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+ mTrace.logTrace(TAG + ".setConnectionCallback",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION
+ | FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ "callback=" + connectionCallback);
+ }
try {
mConnection.setConnectionCallback(connectionCallback);
} catch (RemoteException e) {
@@ -139,25 +184,38 @@ class WindowMagnificationConnectionWrapper {
private static @Nullable
IRemoteMagnificationAnimationCallback transformToRemoteCallback(
- MagnificationAnimationCallback callback) {
+ MagnificationAnimationCallback callback, AccessibilityTraceManager trace) {
if (callback == null) {
return null;
}
- return new RemoteAnimationCallback(callback);
+ return new RemoteAnimationCallback(callback, trace);
}
private static class RemoteAnimationCallback extends
IRemoteMagnificationAnimationCallback.Stub {
-
private final MagnificationAnimationCallback mCallback;
+ private final AccessibilityTraceManager mTrace;
- RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback) {
+ RemoteAnimationCallback(@NonNull MagnificationAnimationCallback callback,
+ @NonNull AccessibilityTraceManager trace) {
mCallback = callback;
+ mTrace = trace;
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK)) {
+ mTrace.logTrace("RemoteAnimationCallback.constructor",
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, "callback=" + callback);
+ }
}
@Override
public void onResult(boolean success) throws RemoteException {
mCallback.onResult(success);
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK)) {
+ mTrace.logTrace("RemoteAnimationCallback.onResult",
+ FLAGS_REMOTE_MAGNIFICATION_ANIMATION_CALLBACK, "success=" + success);
+ }
+
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 4fb9a03b8ac1..b26d36493a3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -34,6 +34,7 @@ import android.view.Display;
import android.view.MotionEvent;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.gestures.MultiTap;
import com.android.server.accessibility.gestures.MultiTapAndHold;
@@ -89,9 +90,10 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl
public WindowMagnificationGestureHandler(@UiContext Context context,
WindowMagnificationManager windowMagnificationMgr,
+ AccessibilityTraceManager trace,
Callback callback,
boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) {
- super(displayId, detectTripleTap, detectShortcutTrigger, callback);
+ super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
Slog.i(mLogTag,
"WindowMagnificationGestureHandler() , displayId = " + displayId + ")");
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 938cb73dac79..7a111d80b42e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -16,6 +16,9 @@
package com.android.server.accessibility.magnification;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -39,6 +42,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.statusbar.StatusBarManagerInternal;
/**
@@ -111,11 +115,14 @@ public class WindowMagnificationManager implements
}
private final Callback mCallback;
+ private final AccessibilityTraceManager mTrace;
- public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback) {
+ public WindowMagnificationManager(Context context, int userId, @NonNull Callback callback,
+ AccessibilityTraceManager trace) {
mContext = context;
mUserId = userId;
mCallback = callback;
+ mTrace = trace;
}
/**
@@ -135,7 +142,7 @@ public class WindowMagnificationManager implements
mConnectionWrapper = null;
}
if (connection != null) {
- mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection);
+ mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace);
}
if (mConnectionWrapper != null) {
@@ -197,7 +204,10 @@ public class WindowMagnificationManager implements
}
}
}
-
+ if (mTrace.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MAGNIFICATION_CONNECTION)) {
+ mTrace.logTrace(TAG + ".requestWindowMagnificationConnection",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION, "connect=" + connect);
+ }
final long identity = Binder.clearCallingIdentity();
try {
final StatusBarManagerInternal service = LocalServices.getService(
@@ -511,6 +521,12 @@ public class WindowMagnificationManager implements
@Override
public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+ mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ "displayId=" + displayId + ";bounds=" + bounds);
+ }
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
@@ -527,11 +543,23 @@ public class WindowMagnificationManager implements
@Override
public void onChangeMagnificationMode(int displayId, int magnificationMode)
throws RemoteException {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+ mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ "displayId=" + displayId + ";mode=" + magnificationMode);
+ }
//TODO: Uses this method to change the magnification mode on non-default display.
}
@Override
public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+ mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ "displayId=" + displayId + ";source=" + sourceBounds);
+ }
synchronized (mLock) {
WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
if (magnifier == null) {
@@ -543,11 +571,23 @@ public class WindowMagnificationManager implements
@Override
public void onPerformScaleAction(int displayId, float scale) {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+ mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ "displayId=" + displayId + ";scale=" + scale);
+ }
mCallback.onPerformScaleAction(displayId, scale);
}
@Override
public void onAccessibilityActionPerformed(int displayId) {
+ if (mTrace.isA11yTracingEnabledForTypes(
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK)) {
+ mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed",
+ FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK,
+ "displayId=" + displayId);
+ }
mCallback.onAccessibilityActionPerformed(displayId);
}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 4946ad442b0a..1af8ad344190 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -187,7 +187,7 @@ public class AppPredictionPerUserService extends
@NonNull IPredictionCallback callback) {
final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
if (sessionInfo == null) return;
- final boolean serviceExists = resolveService(sessionId, false,
+ final boolean serviceExists = resolveService(sessionId, true,
sessionInfo.mUsesPeopleService,
s -> s.registerPredictionUpdates(sessionId, callback));
if (serviceExists) {
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 9ee0159e903a..0b95fefec103 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -4068,14 +4068,20 @@ public class UserBackupManagerService {
}
int operationType;
+ TransportClient transportClient = null;
try {
- operationType = getOperationTypeFromTransport(
- mTransportManager.getTransportClientOrThrow(transport, /* caller */
- "BMS.beginRestoreSession"));
+ transportClient = mTransportManager.getTransportClientOrThrow(
+ transport, /* caller */"BMS.beginRestoreSession");
+ operationType = getOperationTypeFromTransport(transportClient);
} catch (TransportNotAvailableException | TransportNotRegisteredException
| RemoteException e) {
Slog.w(TAG, "Failed to get operation type from transport: " + e);
return null;
+ } finally {
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient,
+ /* caller */"BMS.beginRestoreSession");
+ }
}
synchronized (this) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 30de4b416410..7c1e2da4d6a3 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -457,6 +457,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}, FgThread.getExecutor()).whenComplete(uncheckExceptions((association, err) -> {
if (err == null) {
addAssociation(association, userId);
+ mServiceConnectors.forUser(userId).post(service -> {
+ service.onAssociationCreated();
+ });
} else {
Slog.e(LOG_TAG, "Failed to discover device(s)", err);
callback.onFailure("No devices found: " + err.getMessage());
@@ -566,7 +569,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
return PendingIntent.getActivityAsUser(getContext(),
0 /* request code */,
NotificationAccessConfirmationActivityContract.launcherIntent(
- userId, component, packageTitle),
+ getContext(), userId, component, packageTitle),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT,
null /* options */,
@@ -1448,8 +1451,12 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}
void restartBleScan() {
- mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback);
- startBleScan();
+ if (mBluetoothAdapter.getBluetoothLeScanner() != null) {
+ mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback);
+ startBleScan();
+ } else {
+ Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet).");
+ }
}
private List<ScanFilter> getBleScanFilters() {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 757f1aeb275f..61d784ecb5f3 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -223,7 +223,7 @@ public final class ContentCaptureManagerService extends
@Override // from AbstractMasterSystemService
protected ContentCapturePerUserService newServiceLocked(@UserIdInt int resolvedUserId,
boolean disabled) {
- return new ContentCapturePerUserService(this, mLock, disabled, resolvedUserId);
+ return new ContentCapturePerUserService(this, mLock, disabled, resolvedUserId, mHandler);
}
@Override // from SystemService
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 904def0af2cf..822a42bf50cf 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -46,6 +46,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
@@ -75,6 +76,7 @@ import com.android.server.contentcapture.RemoteContentCaptureService.ContentCapt
import com.android.server.infra.AbstractPerUserSystemService;
import java.io.PrintWriter;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@@ -88,6 +90,10 @@ final class ContentCapturePerUserService
private static final String TAG = ContentCapturePerUserService.class.getSimpleName();
+ private static final int MAX_REBIND_COUNTS = 5;
+ // 5 minutes
+ private static final long REBIND_DURATION_MS = 5 * 60 * 1_000;
+
@GuardedBy("mLock")
private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>();
@@ -121,11 +127,18 @@ final class ContentCapturePerUserService
@GuardedBy("mLock")
private ContentCaptureServiceInfo mInfo;
+ private Instant mLastRebindTime;
+ private int mRebindCount;
+ private final Handler mHandler;
+
+ private final Runnable mReBindServiceRunnable = new RebindServiceRunnable();
+
// TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
ContentCapturePerUserService(@NonNull ContentCaptureManagerService master,
- @NonNull Object lock, boolean disabled, @UserIdInt int userId) {
+ @NonNull Object lock, boolean disabled, @UserIdInt int userId, Handler handler) {
super(master, lock, userId);
+ mHandler = handler;
updateRemoteServiceLocked(disabled);
}
@@ -190,6 +203,43 @@ final class ContentCapturePerUserService
Slog.w(TAG, "remote service died: " + service);
synchronized (mLock) {
mZombie = true;
+ // Reset rebindCount if over 12 hours mLastRebindTime
+ if (mLastRebindTime != null && Instant.now().isAfter(
+ mLastRebindTime.plusMillis(12 * 60 * 60 * 1000))) {
+ if (mMaster.debug) {
+ Slog.i(TAG, "The current rebind count " + mRebindCount + " is reset.");
+ }
+ mRebindCount = 0;
+ }
+ if (mRebindCount >= MAX_REBIND_COUNTS) {
+ writeServiceEvent(
+ FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED,
+ getServiceComponentName());
+ }
+ if (mRebindCount < MAX_REBIND_COUNTS) {
+ mHandler.removeCallbacks(mReBindServiceRunnable);
+ mHandler.postDelayed(mReBindServiceRunnable, REBIND_DURATION_MS);
+ }
+ }
+ }
+
+ private void updateRemoteServiceAndResurrectSessionsLocked() {
+ boolean disabled = !isEnabledLocked();
+ updateRemoteServiceLocked(disabled);
+ resurrectSessionsLocked();
+ }
+
+ private final class RebindServiceRunnable implements Runnable{
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mZombie) {
+ mLastRebindTime = Instant.now();
+ mRebindCount++;
+ updateRemoteServiceAndResurrectSessionsLocked();
+ }
+ }
}
}
@@ -237,8 +287,8 @@ final class ContentCapturePerUserService
}
void onPackageUpdatedLocked() {
- updateRemoteServiceLocked(!isEnabledLocked());
- resurrectSessionsLocked();
+ mRebindCount = 0;
+ updateRemoteServiceAndResurrectSessionsLocked();
}
@GuardedBy("mLock")
@@ -552,6 +602,8 @@ final class ContentCapturePerUserService
mInfo.dump(prefix2, pw);
}
pw.print(prefix); pw.print("Zombie: "); pw.println(mZombie);
+ pw.print(prefix); pw.print("Rebind count: "); pw.println(mRebindCount);
+ pw.print(prefix); pw.print("Last rebind: "); pw.println(mLastRebindTime);
if (mRemoteService != null) {
pw.print(prefix); pw.println("remote service:");
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index 9f3045e68550..3a90a959b877 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -87,7 +87,7 @@ final class ContentCaptureServerSession {
mId = sessionId;
mUid = uid;
mContentCaptureContext = new ContentCaptureContext(/* clientContext= */ null,
- activityId, appComponentName, displayId, flags);
+ activityId, appComponentName, displayId, activityToken, flags);
mSessionStateReceiver = sessionStateReceiver;
try {
sessionStateReceiver.asBinder().linkToDeath(() -> onClientDeath(), 0);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dab95a3cc764..914637caff81 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -152,7 +152,7 @@ java_library_static {
"android.hardware.biometrics.fingerprint-V2.3-java",
"android.hardware.biometrics.fingerprint-V1-java",
"android.hardware.oemlock-V1.0-java",
- "android.hardware.configstore-V1.0-java",
+ "android.hardware.configstore-V1.1-java",
"android.hardware.contexthub-V1.0-java",
"android.hardware.rebootescrow-V1-java",
"android.hardware.soundtrigger-V2.3-java",
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 6f25e8d1acd8..806ea95424a7 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -110,10 +110,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private static final String BLUETOOTH_PRIVILEGED =
android.Manifest.permission.BLUETOOTH_PRIVILEGED;
- private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid";
- private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS = "bluetooth_address";
- private static final String SECURE_SETTINGS_BLUETOOTH_NAME = "bluetooth_name";
-
private static final int ACTIVE_LOG_MAX_SIZE = 20;
private static final int CRASH_LOG_MAX_SIZE = 100;
@@ -643,7 +639,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
if (mContext.getResources()
.getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation)
&& Settings.Secure.getIntForUser(mContentResolver,
- SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 0, mUserId)
+ Settings.Secure.BLUETOOTH_NAME, 0, mUserId)
== 0) {
// if the valid flag is not set, don't load the address and name
if (DBG) {
@@ -652,9 +648,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return;
}
mName = Settings.Secure.getStringForUser(
- mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, mUserId);
+ mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId);
mAddress = Settings.Secure.getStringForUser(
- mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, mUserId);
+ mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId);
if (DBG) {
Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress);
}
@@ -668,30 +664,30 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
*/
private void storeNameAndAddress(String name, String address) {
if (name != null) {
- Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name,
+ Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name,
mUserId);
mName = name;
if (DBG) {
Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser(
- mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME,
+ mContentResolver, Settings.Secure.BLUETOOTH_NAME,
mUserId));
}
}
if (address != null) {
- Settings.Secure.putStringForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS,
+ Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS,
address, mUserId);
mAddress = address;
if (DBG) {
Slog.d(TAG,
"Stored Bluetoothaddress: " + Settings.Secure.getStringForUser(
- mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS,
+ mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS,
mUserId));
}
}
if ((name != null) && (address != null)) {
- Settings.Secure.putIntForUser(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 1,
+ Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1,
mUserId);
}
}
@@ -2080,6 +2076,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mEnable = true;
mQuietEnable = (quietEnable == 1);
+ if (isBle == 0) {
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+
// Use service interface to get the exact state
try {
mBluetoothLock.readLock().lock();
@@ -2093,7 +2093,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
} else {
Slog.w(TAG, "BT Enable in BLE_ON State, going to ON");
mBluetooth.onLeServiceUp(mContext.getAttributionSource());
- persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
}
break;
case BluetoothAdapter.STATE_BLE_TURNING_ON:
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4e5e458d1636..ad1eeb45763f 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -47,6 +47,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.TelecomManager;
+import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
@@ -1025,7 +1026,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
return;
}
- int phoneId = getPhoneIdFromSubId(subId);
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -1048,21 +1048,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
// Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
// force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
} else {//APP specify subID
r.subId = subId;
}
- r.phoneId = phoneId;
+ r.phoneId = getPhoneIdFromSubId(r.subId);
r.eventList = events;
if (DBG) {
- log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
+ log("listen: Register r=" + r + " r.subId=" + r.subId + " r.phoneId=" + r.phoneId);
}
- if (notifyNow && validatePhoneId(phoneId)) {
+ if (notifyNow && validatePhoneId(r.phoneId)) {
if (events.contains(TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)){
try {
- if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]);
- ServiceState rawSs = new ServiceState(mServiceState[phoneId]);
+ if (VDBG) log("listen: call onSSC state=" + mServiceState[r.phoneId]);
+ ServiceState rawSs = new ServiceState(mServiceState[r.phoneId]);
if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
r.callback.onServiceStateChanged(rawSs);
} else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
@@ -1078,8 +1081,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED)) {
try {
- if (mSignalStrength[phoneId] != null) {
- int gsmSignalStrength = mSignalStrength[phoneId]
+ if (mSignalStrength[r.phoneId] != null) {
+ int gsmSignalStrength = mSignalStrength[r.phoneId]
.getGsmSignalStrength();
r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
: gsmSignalStrength));
@@ -1092,7 +1095,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)) {
try {
r.callback.onMessageWaitingIndicatorChanged(
- mMessageWaiting[phoneId]);
+ mMessageWaiting[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1101,7 +1104,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)) {
try {
r.callback.onCallForwardingIndicatorChanged(
- mCallForwarding[phoneId]);
+ mCallForwarding[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1109,11 +1112,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (validateEventAndUserLocked(
r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)) {
try {
- if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[phoneId]);
+ if (DBG_LOC) log("listen: mCellIdentity = " + mCellIdentity[r.phoneId]);
if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
&& checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
// null will be translated to empty CellLocation object in client.
- r.callback.onCellLocationChanged(mCellIdentity[phoneId]);
+ r.callback.onCellLocationChanged(mCellIdentity[r.phoneId]);
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1121,38 +1124,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (events.contains(TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED)) {
try {
- r.callback.onLegacyCallStateChanged(mCallState[phoneId],
- getCallIncomingNumber(r, phoneId));
+ r.callback.onLegacyCallStateChanged(mCallState[r.phoneId],
+ getCallIncomingNumber(r, r.phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_CALL_STATE_CHANGED)) {
try {
- r.callback.onCallStateChanged(mCallState[phoneId]);
+ r.callback.onCallStateChanged(mCallState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)) {
try {
- r.callback.onDataConnectionStateChanged(mDataConnectionState[phoneId],
- mDataConnectionNetworkType[phoneId]);
+ r.callback.onDataConnectionStateChanged(mDataConnectionState[r.phoneId],
+ mDataConnectionNetworkType[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED)) {
try {
- r.callback.onDataActivity(mDataActivity[phoneId]);
+ r.callback.onDataActivity(mDataActivity[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED)) {
try {
- if (mSignalStrength[phoneId] != null) {
- r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ if (mSignalStrength[r.phoneId] != null) {
+ r.callback.onSignalStrengthsChanged(mSignalStrength[r.phoneId]);
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1162,8 +1165,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED)) {
updateReportSignalStrengthDecision(r.subId);
try {
- if (mSignalStrength[phoneId] != null) {
- r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ if (mSignalStrength[r.phoneId] != null) {
+ r.callback.onSignalStrengthsChanged(mSignalStrength[r.phoneId]);
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1172,11 +1175,13 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (validateEventAndUserLocked(
r, TelephonyCallback.EVENT_CELL_INFO_CHANGED)) {
try {
- if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "
- + mCellInfo.get(phoneId));
+ if (DBG_LOC) {
+ log("listen: mCellInfo[" + r.phoneId + "] = "
+ + mCellInfo.get(r.phoneId));
+ }
if (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
&& checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- r.callback.onCellInfoChanged(mCellInfo.get(phoneId));
+ r.callback.onCellInfoChanged(mCellInfo.get(r.phoneId));
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1184,22 +1189,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (events.contains(TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED)) {
try {
- r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
+ r.callback.onPreciseCallStateChanged(mPreciseCallState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)) {
try {
- r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
- mCallPreciseDisconnectCause[phoneId]);
+ r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[r.phoneId],
+ mCallPreciseDisconnectCause[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)) {
try {
- r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(phoneId));
+ r.callback.onImsCallDisconnectCauseChanged(mImsReasonInfo.get(r.phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1208,7 +1213,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)) {
try {
for (PreciseDataConnectionState pdcs
- : mPreciseDataConnectionStates.get(phoneId).values()) {
+ : mPreciseDataConnectionStates.get(r.phoneId).values()) {
r.callback.onPreciseDataConnectionStateChanged(pdcs);
}
} catch (RemoteException ex) {
@@ -1225,29 +1230,29 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)) {
try {
r.callback.onVoiceActivationStateChanged(
- mVoiceActivationState[phoneId]);
+ mVoiceActivationState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED)) {
try {
- r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]);
+ r.callback.onDataActivationStateChanged(mDataActivationState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED)) {
try {
- r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+ r.callback.onUserMobileDataStateChanged(mUserMobileDataState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)) {
try {
- if (mTelephonyDisplayInfos[phoneId] != null) {
- r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[phoneId]);
+ if (mTelephonyDisplayInfos[r.phoneId] != null) {
+ r.callback.onDisplayInfoChanged(mTelephonyDisplayInfos[r.phoneId]);
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1284,20 +1289,20 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (events.contains(TelephonyCallback.EVENT_SRVCC_STATE_CHANGED)) {
try {
- r.callback.onSrvccStateChanged(mSrvccState[phoneId]);
+ r.callback.onSrvccStateChanged(mSrvccState[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if (events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED)) {
- BarringInfo barringInfo = mBarringInfo.get(phoneId);
+ BarringInfo barringInfo = mBarringInfo.get(r.phoneId);
BarringInfo biNoLocation = barringInfo != null
? barringInfo.createLocationInfoSanitizedCopy() : null;
if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
@@ -1315,8 +1320,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.callback.onPhysicalChannelConfigChanged(
shouldSanitizeLocationForPhysicalChannelConfig(r)
? getLocationSanitizedConfigs(
- mPhysicalChannelConfigs.get(phoneId))
- : mPhysicalChannelConfigs.get(phoneId));
+ mPhysicalChannelConfigs.get(r.phoneId))
+ : mPhysicalChannelConfigs.get(r.phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1325,7 +1330,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
TelephonyCallback.EVENT_DATA_ENABLED_CHANGED)) {
try {
r.callback.onDataEnabledChanged(
- mIsDataEnabled[phoneId], mDataEnabledReason[phoneId]);
+ mIsDataEnabled[r.phoneId], mDataEnabledReason[r.phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1333,9 +1338,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (events.contains(
TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
try {
- if (mLinkCapacityEstimateLists.get(phoneId) != null) {
+ if (mLinkCapacityEstimateLists.get(r.phoneId) != null) {
r.callback.onLinkCapacityEstimateChanged(mLinkCapacityEstimateLists
- .get(phoneId));
+ .get(r.phoneId));
}
} catch (RemoteException ex) {
remove(r.binder);
@@ -1577,7 +1582,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
ServiceState stateToSend;
@@ -1639,7 +1644,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if ((activationType == SIM_ACTIVATION_TYPE_VOICE)
&& r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
if (DBG) {
log("notifyVoiceActivationStateForPhoneId: callback.onVASC r=" + r
+ " subId=" + subId + " phoneId=" + phoneId
@@ -1650,7 +1655,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if ((activationType == SIM_ACTIVATION_TYPE_DATA)
&& r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_DATA_ACTIVATION_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
if (DBG) {
log("notifyDataActivationStateForPhoneId: callback.onDASC r=" + r
+ " subId=" + subId + " phoneId=" + phoneId
@@ -1692,7 +1697,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
TelephonyCallback.EVENT_SIGNAL_STRENGTHS_CHANGED)
|| r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_ALWAYS_REPORTED_SIGNAL_STRENGTH_CHANGED))
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (DBG) {
log("notifySignalStrengthForPhoneId: callback.onSsS r=" + r
@@ -1706,7 +1711,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_SIGNAL_STRENGTH_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
int gsmSignalStrength = signalStrength.getGsmSignalStrength();
int ss = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength);
@@ -1753,7 +1758,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onCarrierNetworkChange(active);
} catch (RemoteException ex) {
@@ -1785,7 +1790,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (validateEventAndUserLocked(
r, TelephonyCallback.EVENT_CELL_INFO_CHANGED)
- && idMatch(r.subId, subId, phoneId)
+ && idMatch(r, subId, phoneId)
&& (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
&& checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
try {
@@ -1819,7 +1824,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_MESSAGE_WAITING_INDICATOR_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onMessageWaitingIndicatorChanged(mwi);
} catch (RemoteException ex) {
@@ -1846,7 +1851,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_USER_MOBILE_DATA_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onUserMobileDataStateChanged(state);
} catch (RemoteException ex) {
@@ -1885,7 +1890,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)
- && idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported(
r.callingPackage, Binder.getCallingUserHandle())) {
@@ -1937,7 +1942,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_FORWARDING_INDICATOR_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onCallForwardingIndicatorChanged(cfi);
} catch (RemoteException ex) {
@@ -1966,7 +1971,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
// Notify by correct subId.
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_DATA_ACTIVITY_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onDataActivity(state);
} catch (RemoteException ex) {
@@ -1995,42 +2000,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
ApnSetting apnSetting = preciseState.getApnSetting();
- int apnTypes = apnSetting.getApnTypeBitmask();
- int state = preciseState.getState();
- int networkType = preciseState.getNetworkType();
-
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
- // We only call the callback when the change is for default APN type.
- if ((ApnSetting.TYPE_DEFAULT & apnTypes) != 0
- && (mDataConnectionState[phoneId] != state
- || mDataConnectionNetworkType[phoneId] != networkType)) {
- String str = "onDataConnectionStateChanged("
- + TelephonyUtils.dataStateToString(state)
- + ", " + getNetworkTypeName(networkType)
- + ") subId=" + subId + ", phoneId=" + phoneId;
- log(str);
- mLocalLog.log(str);
- for (Record r : mRecords) {
- if (r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
- try {
- if (DBG) {
- log("Notify data connection state changed on sub: " + subId);
- }
- r.callback.onDataConnectionStateChanged(state, networkType);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
- }
- }
- handleRemoveListLocked();
-
- mDataConnectionState[phoneId] = state;
- mDataConnectionNetworkType[phoneId] = networkType;
- }
-
Pair<Integer, ApnSetting> key = Pair.create(preciseState.getTransportType(),
preciseState.getApnSetting());
PreciseDataConnectionState oldState = mPreciseDataConnectionStates.get(phoneId)
@@ -2039,7 +2010,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_PRECISE_DATA_CONNECTION_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(preciseState);
} catch (RemoteException ex) {
@@ -2062,6 +2033,73 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (preciseState.getState() != TelephonyManager.DATA_DISCONNECTED) {
mPreciseDataConnectionStates.get(phoneId).put(key, preciseState);
}
+
+ // Note that below is just the workaround for reporting the correct data connection
+ // state. The actual fix should be put in the new data stack in T.
+ // TODO: Remove the code below in T.
+
+ // Collect all possible candidate data connection state for internet. Key is the
+ // data connection state, value is the precise data connection state.
+ Map<Integer, PreciseDataConnectionState> internetConnections = new ArrayMap<>();
+ if (preciseState.getState() == TelephonyManager.DATA_DISCONNECTED
+ && preciseState.getApnSetting().getApnTypes()
+ .contains(ApnSetting.TYPE_DEFAULT)) {
+ internetConnections.put(TelephonyManager.DATA_DISCONNECTED, preciseState);
+ }
+ for (Map.Entry<Pair<Integer, ApnSetting>, PreciseDataConnectionState> entry :
+ mPreciseDataConnectionStates.get(phoneId).entrySet()) {
+ if (entry.getKey().first == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+ && entry.getKey().second.getApnTypes()
+ .contains(ApnSetting.TYPE_DEFAULT)) {
+ internetConnections.put(entry.getValue().getState(), entry.getValue());
+ }
+ }
+
+ // If any internet data is in connected state, then report connected, then check
+ // suspended, connecting, disconnecting, and disconnected. The order is very
+ // important.
+ int[] statesInPriority = new int[]{TelephonyManager.DATA_CONNECTED,
+ TelephonyManager.DATA_SUSPENDED, TelephonyManager.DATA_CONNECTING,
+ TelephonyManager.DATA_DISCONNECTING,
+ TelephonyManager.DATA_DISCONNECTED};
+ int state = TelephonyManager.DATA_DISCONNECTED;
+ int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ for (int s : statesInPriority) {
+ if (internetConnections.containsKey(s)) {
+ state = s;
+ networkType = internetConnections.get(s).getNetworkType();
+ break;
+ }
+ }
+
+ if (mDataConnectionState[phoneId] != state
+ || mDataConnectionNetworkType[phoneId] != networkType) {
+ String str = "onDataConnectionStateChanged("
+ + TelephonyUtils.dataStateToString(state)
+ + ", " + TelephonyManager.getNetworkTypeName(networkType)
+ + ") subId=" + subId + ", phoneId=" + phoneId;
+ log(str);
+ mLocalLog.log(str);
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_DATA_CONNECTION_STATE_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ if (DBG) {
+ log("Notify data connection state changed on sub: " + subId);
+ }
+ r.callback.onDataConnectionStateChanged(state, networkType);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+
+ mDataConnectionState[phoneId] = state;
+ mDataConnectionNetworkType[phoneId] = networkType;
+
+ handleRemoveListLocked();
+ }
}
}
}
@@ -2086,7 +2124,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (validateEventAndUserLocked(
r, TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)
- && idMatch(r.subId, subId, phoneId)
+ && idMatch(r, subId, phoneId)
&& (checkCoarseLocationAccess(r, Build.VERSION_CODES.BASE)
&& checkFineLocationAccess(r, Build.VERSION_CODES.Q))) {
try {
@@ -2140,7 +2178,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_PRECISE_CALL_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
} catch (RemoteException ex) {
@@ -2149,7 +2187,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
@@ -2174,7 +2212,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_DISCONNECT_CAUSE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
mCallPreciseDisconnectCause[phoneId]);
@@ -2199,7 +2237,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifyImsCallDisconnectCause: mImsReasonInfo="
@@ -2231,7 +2269,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_SRVCC_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifySrvccStateChanged: mSrvccState=" + state + " r=" + r);
@@ -2260,7 +2298,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if ((r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_OEM_HOOK_RAW))
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onOemHookRawEvent(rawData);
} catch (RemoteException ex) {
@@ -2340,7 +2378,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onRadioPowerStateChanged(state);
} catch (RemoteException ex) {
@@ -2370,7 +2408,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_EMERGENCY_NUMBER_LIST_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
if (VDBG) {
@@ -2457,7 +2495,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
@@ -2488,7 +2526,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_REGISTRATION_FAILURE)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onRegistrationFailed(
checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
@@ -2531,7 +2569,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_BARRING_INFO_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifyBarringInfo: mBarringInfo="
@@ -2576,7 +2614,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (DBG_LOC) {
log("notifyPhysicalChannelConfig: mPhysicalChannelConfigs="
@@ -2643,7 +2681,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_DATA_ENABLED_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onDataEnabledChanged(enabled, reason);
} catch (RemoteException ex) {
@@ -2678,7 +2716,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
if (VDBG) {
log("notifyAllowedNetworkTypesChanged: reason= " + reason
@@ -2720,7 +2758,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)
- && idMatch(r.subId, subId, phoneId)) {
+ && idMatch(r, subId, phoneId)) {
try {
r.callback.onLinkCapacityEstimateChanged(linkCapacityEstimateList);
} catch (RemoteException ex) {
@@ -3189,33 +3227,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
/**
- * If the registrant specified a subId, then we should only notify it if subIds match.
- * If the registrant registered with DEFAULT subId, we should notify only when the related subId
- * is default subId (which could be INVALID if there's no default subId).
+ * Match the sub id or phone id of the event to the record
*
- * This should be the correct way to check record ID match. in idMatch the record's phoneId is
- * speculated based on subId passed by the registrant so it's not a good reference.
- * But to avoid triggering potential regression only replace idMatch with it when an issue with
- * idMatch is reported. Eventually this should replace all instances of idMatch.
+ * We follow the rules below:
+ * 1) If sub id of the event is invalid, phone id should be used.
+ * 2) The event on default sub should be notified to the records
+ * which register the default sub id.
+ * 3) Sub id should be exactly matched for all other cases.
*/
- private boolean idMatchWithoutDefaultPhoneCheck(int subIdInRecord, int subIdToNotify) {
- if (subIdInRecord == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
- return (subIdToNotify == mDefaultSubId);
- } else {
- return (subIdInRecord == subIdToNotify);
- }
- }
-
- boolean idMatch(int rSubId, int subId, int phoneId) {
+ boolean idMatch(Record r, int subId, int phoneId) {
- if(subId < 0) {
- // Invalid case, we need compare phoneId with default one.
- return (mDefaultPhoneId == phoneId);
+ if (subId < 0) {
+ // Invalid case, we need compare phoneId.
+ return (r.phoneId == phoneId);
}
- if(rSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+ if (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
return (subId == mDefaultSubId);
} else {
- return (rSubId == subId);
+ return (r.subId == subId);
}
}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 85eadf5a5137..81627a05c9a4 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1167,11 +1167,16 @@ final class UiModeManagerService extends SystemService {
}
private boolean doesPackageHaveCallingUid(@NonNull String packageName) {
+ int callingUid = mInjector.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ final long ident = Binder.clearCallingIdentity();
try {
- return getContext().getPackageManager().getPackageUid(packageName, 0)
- == mInjector.getCallingUid();
+ return getContext().getPackageManager().getPackageUidAsUser(packageName,
+ callingUserId) == callingUid;
} catch (PackageManager.NameNotFoundException e) {
return false;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index ab6e8202ec8f..7119dc85e2e0 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -148,6 +148,8 @@ public class Watchdog {
);
public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
+ "android.hardware.biometrics.face.IFace/",
+ "android.hardware.biometrics.fingerprint.IFingerprint/",
"android.hardware.light.ILights/",
"android.hardware.power.stats.IPowerStats/",
};
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6e3922746cba..3e905960d4df 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -87,13 +87,13 @@ import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.ForegroundServiceDidNotStartInTimeException;
import android.app.ForegroundServiceStartNotAllowedException;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
import android.app.Service;
import android.app.ServiceStartArgs;
import android.app.admin.DevicePolicyEventLogger;
@@ -158,6 +158,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ServiceState;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
@@ -1278,7 +1279,7 @@ public final class ActiveServices {
}
void killMisbehavingService(ServiceRecord r,
- int appUid, int appPid, String localPackageName) {
+ int appUid, int appPid, String localPackageName, int exceptionTypeId) {
synchronized (mAm) {
if (!r.destroying) {
// This service is still alive, stop it.
@@ -1292,8 +1293,8 @@ public final class ActiveServices {
stopServiceLocked(found, false);
}
}
- mAm.crashApplication(appUid, appPid, localPackageName, -1,
- "Bad notification for startForeground", true /*force*/);
+ mAm.crashApplicationWithType(appUid, appPid, localPackageName, -1,
+ "Bad notification for startForeground", true /*force*/, exceptionTypeId);
}
}
@@ -4385,9 +4386,12 @@ public final class ActiveServices {
if (r.app != null) {
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
- msg.obj = r.app;
- msg.getData().putCharSequence(
- ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = r.app;
+ args.arg2 = r.toString();
+ args.arg3 = r.getComponentName();
+
+ msg.obj = args;
mAm.mHandler.sendMessage(msg);
}
}
@@ -5457,11 +5461,14 @@ public final class ActiveServices {
}
}
- void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) {
- mAm.crashApplicationWithType(app.uid, app.getPid(), app.info.packageName, app.userId,
+ void serviceForegroundCrash(ProcessRecord app, String serviceRecord,
+ ComponentName service) {
+ mAm.crashApplicationWithTypeWithExtras(
+ app.uid, app.getPid(), app.info.packageName, app.userId,
"Context.startForegroundService() did not then call Service.startForeground(): "
+ serviceRecord, false /*force*/,
- ForegroundServiceDidNotStartInTimeException.TYPE_ID);
+ ForegroundServiceDidNotStartInTimeException.TYPE_ID,
+ ForegroundServiceDidNotStartInTimeException.createExtrasForService(service));
}
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 893c068dbdc9..3e3a4a51f372 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -31,6 +31,7 @@ import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.AppOpsManager.OP_NONE;
@@ -91,6 +92,7 @@ import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -182,7 +184,6 @@ import android.app.PendingIntent;
import android.app.ProcessMemoryState;
import android.app.ProfilerInfo;
import android.app.PropertyInvalidatedCache;
-import android.app.RemoteServiceException;
import android.app.SyncNotedAppOp;
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
@@ -300,6 +301,7 @@ import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
+import android.util.FeatureFlagUtils;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
@@ -341,6 +343,7 @@ import com.android.internal.os.BinderTransactionNameResolver;
import com.android.internal.os.ByteTransferPipe;
import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.SomeArgs;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
import com.android.internal.policy.AttributeCache;
@@ -1494,8 +1497,6 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
- static final String SERVICE_RECORD_KEY = "servicerecord";
-
/**
* Flag whether the current user is a "monkey", i.e. whether
* the UI is driven by a UI automation tool.
@@ -1572,6 +1573,13 @@ public class ActivityManagerService extends IActivityManager.Stub
private static final int INDEX_TOTAL_MEMTRACK_GL = 14;
private static final int INDEX_LAST = 15;
+ /**
+ * Used to notify activity lifecycle events.
+ */
+ @Nullable
+ volatile ActivityManagerInternal.VoiceInteractionManagerProvider
+ mVoiceInteractionManagerProvider;
+
final class UiHandler extends Handler {
public UiHandler() {
super(com.android.server.UiThread.get().getLooper(), null, true);
@@ -1667,8 +1675,12 @@ public class ActivityManagerService extends IActivityManager.Stub
mServices.serviceForegroundTimeout((ServiceRecord) msg.obj);
} break;
case SERVICE_FOREGROUND_CRASH_MSG: {
- mServices.serviceForegroundCrash((ProcessRecord) msg.obj,
- msg.getData().getCharSequence(SERVICE_RECORD_KEY));
+ SomeArgs args = (SomeArgs) msg.obj;
+ mServices.serviceForegroundCrash(
+ (ProcessRecord) args.arg1,
+ (String) args.arg2,
+ (ComponentName) args.arg3);
+ args.recycle();
} break;
case UPDATE_TIME_ZONE: {
synchronized (mProcLock) {
@@ -1908,6 +1920,14 @@ public class ActivityManagerService extends IActivityManager.Stub
return mAppOpsService;
}
+ /**
+ * Sets the internal voice interaction manager service.
+ */
+ private void setVoiceInteractionManagerProvider(
+ @Nullable ActivityManagerInternal.VoiceInteractionManagerProvider provider) {
+ mVoiceInteractionManagerProvider = provider;
+ }
+
static class MemBinder extends Binder {
ActivityManagerService mActivityManagerService;
private final PriorityDump.PriorityDumper mPriorityDumper =
@@ -2760,6 +2780,11 @@ public class ActivityManagerService extends IActivityManager.Stub
|| event == Event.ACTIVITY_DESTROYED)) {
contentCaptureService.notifyActivityEvent(userId, activity, event);
}
+ // TODO(b/201234353): Move the logic to client side.
+ if (mVoiceInteractionManagerProvider != null && (event == Event.ACTIVITY_PAUSED
+ || event == Event.ACTIVITY_RESUMED || event == Event.ACTIVITY_STOPPED)) {
+ mVoiceInteractionManagerProvider.notifyActivityEventChanged();
+ }
}
/**
@@ -3042,15 +3067,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void crashApplication(int uid, int initialPid, String packageName, int userId,
- String message, boolean force) {
- crashApplicationWithType(uid, initialPid, packageName, userId, message, force,
- RemoteServiceException.TYPE_ID);
+ public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId,
+ String message, boolean force, int exceptionTypeId) {
+ crashApplicationWithTypeWithExtras(uid, initialPid, packageName, userId, message,
+ force, exceptionTypeId, null);
}
@Override
- public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId,
- String message, boolean force, int exceptionTypeId) {
+ public void crashApplicationWithTypeWithExtras(int uid, int initialPid, String packageName,
+ int userId, String message, boolean force, int exceptionTypeId,
+ @Nullable Bundle extras) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: crashApplication() from pid="
@@ -3063,7 +3089,7 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized(this) {
mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId,
- message, force, exceptionTypeId);
+ message, force, exceptionTypeId, extras);
}
}
@@ -4236,7 +4262,7 @@ public class ActivityManagerService extends IActivityManager.Stub
didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
- evenPersistent, true /* setRemoved */,
+ evenPersistent, true /* setRemoved */, uninstalling,
packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
: ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_UNKNOWN,
@@ -4896,6 +4922,8 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void bootAnimationComplete() {
+ if (DEBUG_ALL) Slog.d(TAG, "bootAnimationComplete: Callers=" + Debug.getCallers(4));
+
final boolean callFinishBooting;
synchronized (this) {
callFinishBooting = mCallFinishBooting;
@@ -7284,6 +7312,7 @@ public class ActivityManagerService extends IActivityManager.Stub
ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
true /* callerWillRestart */, true /* doit */,
true /* evenPersistent */, false /* setRemoved */,
+ false /* uninstalling */,
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_KILL_UID,
reason != null ? reason : "kill uid");
@@ -7305,6 +7334,7 @@ public class ActivityManagerService extends IActivityManager.Stub
ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
true /* callerWillRestart */, true /* doit */,
true /* evenPersistent */, false /* setRemoved */,
+ false /* uninstalling */,
ApplicationExitInfo.REASON_PERMISSION_CHANGE,
ApplicationExitInfo.SUBREASON_UNKNOWN,
reason != null ? reason : "kill uid");
@@ -7718,7 +7748,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// On Automotive, at this point the system user has already been started and unlocked,
// and some of the tasks we do here have already been done. So skip those in that case.
- // TODO(b/132262830): this workdound shouldn't be necessary once we move the
+ // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the
// headless-user start logic to UserManager-land
final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
@@ -14360,6 +14390,8 @@ public class ActivityManagerService extends IActivityManager.Stub
private void checkExcessivePowerUsage() {
updateCpuStatsNow();
+ final boolean monitorPhantomProcs = mSystemReady && FeatureFlagUtils.isEnabled(mContext,
+ SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
synchronized (mProcLock) {
final boolean doCpuKills = mLastPowerCheckUptime != 0;
final long curUptime = SystemClock.uptimeMillis();
@@ -14385,9 +14417,11 @@ public class ActivityManagerService extends IActivityManager.Stub
updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app);
- // Also check the phantom processes if there is any
- updatePhantomProcessCpuTimeLPr(
- uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+ if (monitorPhantomProcs) {
+ // Also check the phantom processes if there is any
+ updatePhantomProcessCpuTimeLPr(
+ uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+ }
}
});
}
@@ -15188,6 +15222,21 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
+ public String getSwitchingFromUserMessage() {
+ return mUserController.getSwitchingFromSystemUserMessage();
+ }
+
+ @Override
+ public String getSwitchingToUserMessage() {
+ return mUserController.getSwitchingToSystemUserMessage();
+ }
+
+ @Override
+ public void setStopUserOnSwitch(@StopUserOnSwitch int value) {
+ mUserController.setStopUserOnSwitch(value);
+ }
+
+ @Override
public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
/* callback= */ callback, /* keyEvictedCallback= */ null);
@@ -16474,6 +16523,26 @@ public class ActivityManagerService extends IActivityManager.Stub
return mProcessList.getIsolatedProcessesLocked(uid);
}
}
+
+ /** @see ActivityManagerService#sendIntentSender */
+ @Override
+ public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,
+ Intent intent, String resolvedType,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+ return ActivityManagerService.this.sendIntentSender(target, allowlistToken, code,
+ intent, resolvedType, finishedReceiver, requiredPermission, options);
+ }
+
+ @Override
+ public void setVoiceInteractionManagerProvider(
+ @Nullable ActivityManagerInternal.VoiceInteractionManagerProvider provider) {
+ ActivityManagerService.this.setVoiceInteractionManagerProvider(provider);
+ }
+
+ @Override
+ public void setStopUserOnSwitch(int value) {
+ ActivityManagerService.this.setStopUserOnSwitch(value);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 685d606f8d41..ea28117a6a3d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -29,6 +29,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.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
import android.app.ActivityManager;
@@ -44,6 +46,7 @@ import android.app.IStopUserCallback;
import android.app.IUidObserver;
import android.app.KeyguardManager;
import android.app.ProfilerInfo;
+import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.UserSwitchObserver;
import android.app.WaitResult;
import android.app.usage.AppStandbyInfo;
@@ -100,6 +103,7 @@ import com.android.internal.util.HexDump;
import com.android.internal.util.MemInfoReader;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.compat.PlatformCompat;
+import com.android.server.utils.Slogf;
import java.io.BufferedReader;
import java.io.IOException;
@@ -128,6 +132,10 @@ import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
final class ActivityManagerShellCommand extends ShellCommand {
+
+ static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerShellCommand" : TAG_AM;
+
+
public static final String NO_CLASS_ERROR_CODE = "Error type 3";
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -323,6 +331,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runServiceRestartBackoff(pw);
case "get-isolated-pids":
return runGetIsolatedProcesses(pw);
+ case "set-stop-user-on-switch":
+ return runSetStopUserOnSwitch(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -1145,7 +1155,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
} catch (NumberFormatException e) {
packageName = arg;
}
- mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash", false);
+ mInterface.crashApplicationWithType(-1, pid, packageName, userId, "shell-induced crash",
+ false, CrashedByAdbException.TYPE_ID);
return 0;
}
@@ -3157,6 +3168,29 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException {
+ mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "setStopUserOnSwitch()");
+ String arg = getNextArg();
+ if (arg == null) {
+ Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value");
+ mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT);
+ pw.println("Reset to default value");
+ return 0;
+ }
+
+ boolean stop = Boolean.parseBoolean(arg);
+ int value = stop
+ ? ActivityManager.STOP_USER_ON_SWITCH_TRUE
+ : ActivityManager.STOP_USER_ON_SWITCH_FALSE;
+
+ Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop);
+ mInternal.setStopUserOnSwitch(value);
+ pw.println("Set to " + stop);
+
+ return 0;
+ }
+
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
Configuration config = mInterface.getConfiguration();
@@ -3489,6 +3523,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Shows the restart backoff policy state for <PACKAGE_NAME>.");
pw.println(" get-isolated-pids <UID>");
pw.println(" Get the PIDs of isolated processes with packages in this <UID>");
+ pw.println(" set-stop-user-on-switch [true|false]");
+ pw.println(" Sets whether the current user (and its profiles) should be stopped"
+ + " when switching to a different user.");
+ pw.println(" Without arguments, it resets to the value defined by platform.");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index bcb42bb38495..0bf0fe2be246 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -27,6 +27,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AnrController;
@@ -40,6 +41,7 @@ import android.content.pm.VersionedPackage;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
@@ -489,7 +491,7 @@ class AppErrors {
* @param message
*/
void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId,
- String message, boolean force, int exceptionTypeId) {
+ String message, boolean force, int exceptionTypeId, @Nullable Bundle extras) {
ProcessRecord proc = null;
// Figure out which process to kill. We don't trust that initialPid
@@ -521,7 +523,7 @@ class AppErrors {
return;
}
- proc.scheduleCrashLocked(message, exceptionTypeId);
+ proc.scheduleCrashLocked(message, exceptionTypeId, extras);
if (force) {
// If the app is responsive, the scheduled crash will happen as expected
// and then the delayed summary kill will be a no-op.
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 505edf989557..f51bec423787 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -19,6 +19,7 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -77,6 +78,7 @@ import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
+import android.util.FeatureFlagUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -1038,6 +1040,7 @@ public class AppProfiler {
mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
state.setProcStateChanged(false);
}
+ trimMemoryUiHiddenIfNecessaryLSP(app);
if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
try {
@@ -1080,24 +1083,6 @@ public class AppProfiler {
}
profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
} else {
- if ((curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
- // If this application is now in the background and it
- // had done UI, then give it the special trim level to
- // have it free UI resources.
- final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
- if (trimMemoryLevel < level && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
- + app.processName + " to " + level);
- }
- thread.scheduleTrimMemory(level);
- } catch (RemoteException e) {
- }
- }
- profile.setPendingUiClean(false);
- }
if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
try {
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
@@ -1124,28 +1109,36 @@ public class AppProfiler {
mService.setProcessTrackerStateLOSP(app, trackerMemFactor, now);
state.setProcStateChanged(false);
}
- if ((state.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- || state.isSystemNoUi()) && profile.hasPendingUiClean()) {
- if (profile.getTrimMemoryLevel() < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
- && (thread = app.getThread()) != null) {
- try {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
- Slog.v(TAG_OOM_ADJ,
- "Trimming memory of ui hidden " + app.processName
- + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- }
- thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- } catch (RemoteException e) {
- }
- }
- profile.setPendingUiClean(false);
- }
+ trimMemoryUiHiddenIfNecessaryLSP(app);
profile.setTrimMemoryLevel(0);
});
}
return allChanged;
}
+ @GuardedBy({"mService", "mProcLock"})
+ private void trimMemoryUiHiddenIfNecessaryLSP(ProcessRecord app) {
+ if ((app.mState.getCurProcState() >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) {
+ // If this application is now in the background and it
+ // had done UI, then give it the special trim level to
+ // have it free UI resources.
+ final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
+ IApplicationThread thread;
+ if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
+ try {
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
+ Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
+ + app.processName + " to " + level);
+ }
+ thread.scheduleTrimMemory(level);
+ } catch (RemoteException e) {
+ }
+ }
+ app.mProfile.setPendingUiClean(false);
+ }
+ }
+
@GuardedBy("mProcLock")
long getLowRamTimeSinceIdleLPr(long now) {
return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
@@ -1797,6 +1790,8 @@ public class AppProfiler {
}
void updateCpuStatsNow() {
+ final boolean monitorPhantomProcs = mService.mSystemReady && FeatureFlagUtils.isEnabled(
+ mService.mContext, SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
synchronized (mProcessCpuTracker) {
mProcessCpuMutexFree.set(false);
final long now = SystemClock.uptimeMillis();
@@ -1835,7 +1830,7 @@ public class AppProfiler {
}
}
- if (haveNewCpuStats) {
+ if (monitorPhantomProcs && haveNewCpuStats) {
mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker);
}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 7ba032f683b8..cf4c8a356662 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -114,6 +114,9 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
private int mScreenState;
@GuardedBy("this")
+ private int[] mPerDisplayScreenStates = null;
+
+ @GuardedBy("this")
private boolean mUseLatestStates = true;
@GuardedBy("this")
@@ -291,8 +294,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
@Override
- public Future<?> scheduleSyncDueToScreenStateChange(
- int flags, boolean onBattery, boolean onBatteryScreenOff, int screenState) {
+ public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
+ boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
synchronized (BatteryExternalStatsWorker.this) {
if (mCurrentFuture == null || (mUpdateFlags & UPDATE_CPU) == 0) {
mOnBattery = onBattery;
@@ -301,6 +304,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
// always update screen state
mScreenState = screenState;
+ mPerDisplayScreenStates = perDisplayScreenStates;
return scheduleSyncLocked("screen-state", flags);
}
}
@@ -432,6 +436,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
final boolean onBattery;
final boolean onBatteryScreenOff;
final int screenState;
+ final int[] displayScreenStates;
final boolean useLatestStates;
synchronized (BatteryExternalStatsWorker.this) {
updateFlags = mUpdateFlags;
@@ -440,6 +445,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
onBattery = mOnBattery;
onBatteryScreenOff = mOnBatteryScreenOff;
screenState = mScreenState;
+ displayScreenStates = mPerDisplayScreenStates;
useLatestStates = mUseLatestStates;
mUpdateFlags = 0;
mCurrentReason = null;
@@ -461,7 +467,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
}
try {
updateExternalStatsLocked(reason, updateFlags, onBattery,
- onBatteryScreenOff, screenState, useLatestStates);
+ onBatteryScreenOff, screenState, displayScreenStates,
+ useLatestStates);
} finally {
if (DEBUG) {
Slog.d(TAG, "end updateExternalStatsSync");
@@ -506,7 +513,8 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
@GuardedBy("mWorkerLock")
private void updateExternalStatsLocked(final String reason, int updateFlags, boolean onBattery,
- boolean onBatteryScreenOff, int screenState, boolean useLatestStates) {
+ boolean onBatteryScreenOff, int screenState, int[] displayScreenStates,
+ boolean useLatestStates) {
// We will request data from external processes asynchronously, and wait on a timeout.
SynchronousResultReceiver wifiReceiver = null;
SynchronousResultReceiver bluetoothReceiver = null;
@@ -659,11 +667,12 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
// Inform mStats about each applicable measured energy (unless addressed elsewhere).
if (measuredEnergyDeltas != null) {
- final long displayChargeUC = measuredEnergyDeltas.displayChargeUC;
- if (displayChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) {
- // If updating, pass in what BatteryExternalStatsWorker thinks screenState is.
- mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC, screenState,
- elapsedRealtime);
+ final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC;
+ if (displayChargeUC != null && displayChargeUC.length > 0) {
+ // If updating, pass in what BatteryExternalStatsWorker thinks
+ // displayScreenStates is.
+ mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC,
+ displayScreenStates, elapsedRealtime);
}
final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC;
@@ -948,6 +957,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
switch (consumer.type) {
case EnergyConsumerType.OTHER:
case EnergyConsumerType.CPU_CLUSTER:
+ case EnergyConsumerType.DISPLAY:
break;
default:
Slog.w(TAG, "EnergyConsumer '" + consumer.name + "' has unexpected ordinal "
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ae14ca7b66bd..8d10d562520a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1215,7 +1215,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
mHandler.post(() -> {
if (DBG) Slog.d(TAG, "begin noteScreenState");
synchronized (mStats) {
- mStats.noteScreenStateLocked(state, elapsedRealtime, uptime, currentTime);
+ mStats.noteScreenStateLocked(0, state, elapsedRealtime, uptime, currentTime);
}
if (DBG) Slog.d(TAG, "end noteScreenState");
});
@@ -1230,7 +1230,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
final long uptime = SystemClock.uptimeMillis();
mHandler.post(() -> {
synchronized (mStats) {
- mStats.noteScreenBrightnessLocked(brightness, elapsedRealtime, uptime);
+ mStats.noteScreenBrightnessLocked(0, brightness, elapsedRealtime, uptime);
}
});
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 94bf62f8b9b7..2da41070a6f4 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -20,7 +20,13 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -29,6 +35,7 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -603,7 +610,8 @@ public final class BroadcastQueue {
synchronized (mService) {
Slog.w(TAG, "Can't deliver broadcast to " + app.processName
+ " (pid " + app.getPid() + "). Crashing it.");
- app.scheduleCrashLocked("can't deliver broadcast");
+ app.scheduleCrashLocked("can't deliver broadcast",
+ CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
}
throw ex;
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c641bf04731f..2761a7c6d937 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -612,6 +612,8 @@ public final class CachedAppOptimizer {
*/
static private native void compactProcess(int pid, int compactionFlags);
+ static private native void cancelCompaction();
+
/**
* Reads the flag value from DeviceConfig to determine whether app compaction
* should be enabled, and starts the freeze/compaction thread if needed.
@@ -999,7 +1001,7 @@ public final class CachedAppOptimizer {
void unfreezeTemporarily(ProcessRecord app) {
if (mUseFreezer) {
synchronized (mProcLock) {
- if (app.mOptRecord.isFrozen()) {
+ if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
unfreezeAppLSP(app);
freezeAppAsyncLSP(app);
}
@@ -1125,6 +1127,26 @@ public final class CachedAppOptimizer {
}
}
+ @GuardedBy({"mService", "mProcLock"})
+ void onOomAdjustChanged(int oldAdj, int newAdj, ProcessRecord app) {
+ // Cancel any currently executing compactions
+ // if the process moved out of cached state
+ if (DefaultProcessDependencies.mPidCompacting == app.mPid && newAdj < oldAdj
+ && newAdj < ProcessList.CACHED_APP_MIN_ADJ) {
+ cancelCompaction();
+ }
+
+ // Perform a minor compaction when a perceptible app becomes the prev/home app
+ // Perform a major compaction when any app enters cached
+ if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
+ && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) {
+ compactAppSome(app);
+ } else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ
+ && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
+ compactAppFull(app);
+ }
+ }
+
@VisibleForTesting
static final class LastCompactionStats {
private final long[] mRssAfterCompaction;
@@ -1167,6 +1189,13 @@ public final class CachedAppOptimizer {
name = proc.processName;
opt.setHasPendingCompact(false);
+ if (mAm.mInternal.isPendingTopUid(proc.uid)) {
+ // In case the OOM Adjust has not yet been propagated we see if this is
+ // pending on becoming top app in which case we should not compact.
+ Slog.e(TAG_AM, "Skip compaction since UID is active for " + name);
+ return;
+ }
+
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
if ((pendingAction == COMPACT_PROCESS_SOME
@@ -1576,6 +1605,8 @@ public final class CachedAppOptimizer {
* Default implementation for ProcessDependencies, public vor visibility to OomAdjuster class.
*/
private static final class DefaultProcessDependencies implements ProcessDependencies {
+ public static int mPidCompacting = -1;
+
// Get memory RSS from process.
@Override
public long[] getRss(int pid) {
@@ -1585,6 +1616,7 @@ public final class CachedAppOptimizer {
// Compact process.
@Override
public void performCompaction(String action, int pid) throws IOException {
+ mPidCompacting = pid;
if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])) {
compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG);
} else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FILE])) {
@@ -1592,6 +1624,7 @@ public final class CachedAppOptimizer {
} else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) {
compactProcess(pid, COMPACT_ACTION_ANON_FLAG);
}
+ mPidCompacting = -1;
}
}
}
diff --git a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
index a9fca4f24026..0359aa531c64 100644
--- a/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/am/MeasuredEnergySnapshot.java
@@ -49,6 +49,9 @@ public class MeasuredEnergySnapshot {
/** Number of ordinals for {@link EnergyConsumerType#CPU_CLUSTER}. */
private final int mNumCpuClusterOrdinals;
+ /** Number of ordinals for {@link EnergyConsumerType#DISPLAY}. */
+ private final int mNumDisplayOrdinals;
+
/** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
private final int mNumOtherOrdinals;
@@ -95,6 +98,7 @@ public class MeasuredEnergySnapshot {
mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER,
idToConsumerMap);
+ mNumDisplayOrdinals = calculateNumOrdinals(EnergyConsumerType.DISPLAY, idToConsumerMap);
mNumOtherOrdinals = calculateNumOrdinals(EnergyConsumerType.OTHER, idToConsumerMap);
mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
}
@@ -108,7 +112,7 @@ public class MeasuredEnergySnapshot {
public long[] cpuClusterChargeUC = null;
/** The chargeUC for {@link EnergyConsumerType#DISPLAY}. */
- public long displayChargeUC = UNAVAILABLE;
+ public long[] displayChargeUC = null;
/** The chargeUC for {@link EnergyConsumerType#GNSS}. */
public long gnssChargeUC = UNAVAILABLE;
@@ -212,7 +216,10 @@ public class MeasuredEnergySnapshot {
break;
case EnergyConsumerType.DISPLAY:
- output.displayChargeUC = deltaChargeUC;
+ if (output.displayChargeUC == null) {
+ output.displayChargeUC = new long[mNumDisplayOrdinals];
+ }
+ output.displayChargeUC[ordinal] = deltaChargeUC;
break;
case EnergyConsumerType.GNSS:
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index d993772b4f3b..e94c2fa60fc1 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1711,7 +1711,7 @@ public class OomAdjuster {
int schedGroup;
int procState;
int cachedAdjSeq;
- int capability = 0;
+ int capability = cycleReEval ? app.mState.getCurCapability() : 0;
boolean foregroundActivities = false;
boolean hasVisibleActivities = false;
@@ -2115,10 +2115,6 @@ public class OomAdjuster {
}
if ((cr.flags & Context.BIND_WAIVE_PRIORITY) == 0) {
- if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
- continue;
- }
-
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
capability |= cstate.getCurCapability();
}
@@ -2139,6 +2135,10 @@ public class OomAdjuster {
}
}
+ if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
+ continue;
+ }
+
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. The specific cached state
@@ -2703,18 +2703,9 @@ public class OomAdjuster {
// don't compact during bootup
if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
// Cached and prev/home compaction
+ // reminder: here, setAdj is previous state, curAdj is upcoming state
if (state.getCurAdj() != state.getSetAdj()) {
- // Perform a minor compaction when a perceptible app becomes the prev/home app
- // Perform a major compaction when any app enters cached
- // reminder: here, setAdj is previous state, curAdj is upcoming state
- if (state.getSetAdj() <= ProcessList.PERCEPTIBLE_APP_ADJ
- && (state.getCurAdj() == ProcessList.PREVIOUS_APP_ADJ
- || state.getCurAdj() == ProcessList.HOME_APP_ADJ)) {
- mCachedAppOptimizer.compactAppSome(app);
- } else if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ
- && state.getCurAdj() <= ProcessList.CACHED_APP_MAX_ADJ) {
- mCachedAppOptimizer.compactAppFull(app);
- }
+ mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app);
} else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
&& state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ
// Because these can fire independent of oom_adj/procstate changes, we need
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index b07684c9a004..2ec1aedd18f9 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -28,6 +29,7 @@ import android.app.ApplicationExitInfo.SubReason;
import android.os.Handler;
import android.os.Process;
import android.os.StrictMode;
+import android.util.FeatureFlagUtils;
import android.util.Slog;
import android.util.SparseArray;
@@ -419,6 +421,10 @@ public final class PhantomProcessList {
* order of the oom adjs of their parent process.
*/
void trimPhantomProcessesIfNecessary() {
+ if (!mService.mSystemReady || !FeatureFlagUtils.isEnabled(mService.mContext,
+ SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS)) {
+ return;
+ }
synchronized (mService.mProcLock) {
synchronized (mLock) {
mTrimPhantomProcessScheduled = false;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ee226f6ae6c4..29ac8738eeab 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -375,6 +375,16 @@ public final class ProcessList {
private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
/**
+ * Native heap allocations in AppZygote process and its descendants will now have a
+ * non-zero tag in the most significant byte.
+ * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+ * Pointers</a>
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+ private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677;
+
+ /**
* Enable asynchronous (ASYNC) memory tag checking in this process. This
* flag will only have an effect on hardware supporting the ARM Memory
* Tagging Extension (MTE).
@@ -1744,6 +1754,16 @@ public final class ProcessList {
return level;
}
+ private int decideTaggingLevelForAppZygote(ProcessRecord app) {
+ int level = decideTaggingLevel(app);
+ // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
+ if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info)
+ && level == Zygote.MEMORY_TAG_LEVEL_TBI) {
+ level = Zygote.MEMORY_TAG_LEVEL_NONE;
+ }
+ return level;
+ }
+
private int decideGwpAsanLevel(ProcessRecord app) {
// Look at the process attribute first.
if (app.processInfo != null
@@ -2244,7 +2264,8 @@ public final class ProcessList {
// not the calling one.
appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
appInfo.uid = uid;
- appZygote = new AppZygote(appInfo, uid, firstUid, lastUid);
+ int runtimeFlags = decideTaggingLevelForAppZygote(app);
+ appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
mAppZygotes.put(app.info.processName, uid, appZygote);
zygoteProcessList = new ArrayList<ProcessRecord>();
mAppZygoteProcesses.put(appZygote, zygoteProcessList);
@@ -2766,8 +2787,8 @@ public final class ProcessList {
int reasonCode, int subReason, String reason) {
return killPackageProcessesLSP(packageName, appId, userId, minOomAdj,
false /* callerWillRestart */, true /* allowRestart */, true /* doit */,
- false /* evenPersistent */, false /* setRemoved */, reasonCode,
- subReason, reason);
+ false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */,
+ reasonCode, subReason, reason);
}
@GuardedBy("mService")
@@ -2800,9 +2821,10 @@ public final class ProcessList {
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
- boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode,
- int subReason, String reason) {
- ArrayList<ProcessRecord> procs = new ArrayList<>();
+ boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling,
+ int reasonCode, int subReason, String reason) {
+ final PackageManagerInternal pm = mService.getPackageManagerInternal();
+ final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>();
// Remove all processes this package may have touched: all with the
// same UID (except for the system or root user), and all whose name
@@ -2819,7 +2841,18 @@ public final class ProcessList {
}
if (app.isRemoved()) {
if (doit) {
- procs.add(app);
+ boolean shouldAllowRestart = false;
+ if (!uninstalling && packageName != null) {
+ // This package has a dependency on the given package being stopped,
+ // while it's not being frozen nor uninstalled, allow to restart it.
+ shouldAllowRestart = !app.getPkgList().containsKey(packageName)
+ && app.getPkgDeps() != null
+ && app.getPkgDeps().contains(packageName)
+ && app.info != null
+ && !pm.isPackageFrozen(app.info.packageName, app.uid,
+ app.userId);
+ }
+ procs.add(new Pair<>(app, shouldAllowRestart));
}
continue;
}
@@ -2834,6 +2867,8 @@ public final class ProcessList {
continue;
}
+ boolean shouldAllowRestart = false;
+
// If no package is specified, we call all processes under the
// give user id.
if (packageName == null) {
@@ -2855,9 +2890,16 @@ public final class ProcessList {
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
}
- if (!app.getPkgList().containsKey(packageName) && !isDep) {
+ final boolean isInPkgList = app.getPkgList().containsKey(packageName);
+ if (!isInPkgList && !isDep) {
continue;
}
+ if (!isInPkgList && isDep && !uninstalling && app.info != null
+ && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) {
+ // This package has a dependency on the given package being stopped,
+ // while it's not being frozen nor uninstalled, allow to restart it.
+ shouldAllowRestart = true;
+ }
}
// Process has passed all conditions, kill it!
@@ -2867,14 +2909,15 @@ public final class ProcessList {
if (setRemoved) {
app.setRemoved(true);
}
- procs.add(app);
+ procs.add(new Pair<>(app, shouldAllowRestart));
}
}
int N = procs.size();
for (int i=0; i<N; i++) {
- removeProcessLocked(procs.get(i), callerWillRestart, allowRestart,
- reasonCode, subReason, reason);
+ final Pair<ProcessRecord, Boolean> proc = procs.get(i);
+ removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+ reasonCode, subReason, reason);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 13556c09215f..dd2c810750ac 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -26,12 +26,12 @@ import android.app.ApplicationExitInfo;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
-import android.app.RemoteServiceException;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProcessInfo;
import android.content.pm.VersionedPackage;
import android.content.res.CompatibilityInfo;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
@@ -979,11 +979,6 @@ class ProcessRecord implements WindowProcessListener {
return mServices.hasForegroundServices();
}
- @GuardedBy("mService")
- void scheduleCrashLocked(String message) {
- scheduleCrashLocked(message, RemoteServiceException.TYPE_ID);
- }
-
/**
* Let an app process throw an exception on a binder thread, which typically crashes the
* process, unless it has an unhandled exception handler.
@@ -995,7 +990,7 @@ class ProcessRecord implements WindowProcessListener {
* of its subclasses.
*/
@GuardedBy("mService")
- void scheduleCrashLocked(String message, int exceptionTypeId) {
+ void scheduleCrashLocked(String message, int exceptionTypeId, @Nullable Bundle extras) {
// Checking killedbyAm should keep it from showing the crash dialog if the process
// was already dead for a good / normal reason.
if (!mKilledByAm) {
@@ -1006,7 +1001,7 @@ class ProcessRecord implements WindowProcessListener {
}
final long ident = Binder.clearCallingIdentity();
try {
- mThread.scheduleCrash(message, exceptionTypeId);
+ mThread.scheduleCrash(message, exceptionTypeId, extras);
} catch (RemoteException e) {
// If it's already dead our work is done. If it's wedged just kill it.
// We won't get the crash dialog or the error reporting.
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 17930ea9c93c..e36898fee387 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -28,6 +28,7 @@ import android.annotation.Nullable;
import android.app.IApplicationThread;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1039,7 +1040,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// If it gave us a garbage notification, it doesn't
// get to be foreground.
ams.mServices.killMisbehavingService(record,
- appUid, appPid, localPackageName);
+ appUid, appPid, localPackageName,
+ CannotPostForegroundServiceNotificationException.TYPE_ID);
}
}
});
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index ba3e1fb95e7d..b28b1a66cd97 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -19,6 +19,9 @@ package com.android.server.am;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
+import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
+import static android.app.ActivityManager.StopUserOnSwitch;
import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
@@ -368,6 +371,13 @@ class UserController implements Handler.Callback {
@GuardedBy("mLock")
private boolean mInitialized;
+ /**
+ * Defines the behavior of whether the background users should be stopped when the foreground
+ * user is switched.
+ */
+ @GuardedBy("mLock")
+ private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT;
+
UserController(ActivityManagerService service) {
this(new Injector(service));
}
@@ -408,8 +418,31 @@ class UserController implements Handler.Callback {
}
}
- private boolean shouldStopBackgroundUsersOnSwitch() {
- int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1);
+ void setStopUserOnSwitch(@StopUserOnSwitch int value) {
+ if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
+ == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException(
+ "You either need MANAGE_USERS or INTERACT_ACROSS_USERS permission to "
+ + "call setStopUserOnSwitch()");
+ }
+
+ synchronized (mLock) {
+ Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value);
+ mStopUserOnSwitch = value;
+ }
+ }
+
+ private boolean shouldStopUserOnSwitch() {
+ synchronized (mLock) {
+ if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) {
+ final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE;
+ Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value);
+ return value;
+ }
+ }
+ final int property = SystemProperties.getInt("fw.stop_bg_users_on_switch", -1);
return property == -1 ? mDelayUserDataLocking : property == 1;
}
@@ -1759,7 +1792,8 @@ class UserController implements Handler.Callback {
private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second,
- getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage());
+ getSwitchingFromSystemUserMessageUnchecked(),
+ getSwitchingToSystemUserMessageUnchecked());
}
private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
@@ -1799,7 +1833,7 @@ class UserController implements Handler.Callback {
mUserSwitchObservers.finishBroadcast();
}
- private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) {
+ private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) {
// Never stop system user
if (oldUserId == UserHandle.USER_SYSTEM) {
return;
@@ -1807,18 +1841,17 @@ class UserController implements Handler.Callback {
boolean hasRestriction =
hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId);
synchronized (mLock) {
- // If running in background is disabled or mStopBackgroundUsersOnSwitch mode,
- // stop the user.
- boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch();
+ // If running in background is disabled or mStopUserOnSwitch mode, stop the user.
+ boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch();
if (!disallowRunInBg) {
if (DEBUG_MU) {
- Slogf.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related "
- + "users", oldUserId);
+ Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users",
+ oldUserId);
}
return;
}
if (DEBUG_MU) {
- Slogf.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users",
+ Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users",
oldUserId);
}
stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
@@ -1921,7 +1954,7 @@ class UserController implements Handler.Callback {
mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
stopGuestOrEphemeralUserIfBackground(oldUserId);
- stopBackgroundUsersOnSwitchIfEnforced(oldUserId);
+ stopUserOnSwitchIfEnforced(oldUserId);
}
private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
@@ -2532,18 +2565,40 @@ class UserController implements Handler.Callback {
}
}
- private String getSwitchingFromSystemUserMessage() {
+ // Called by AMS, must check permission
+ String getSwitchingFromSystemUserMessage() {
+ checkHasManageUsersPermission("getSwitchingFromSystemUserMessage()");
+
+ return getSwitchingFromSystemUserMessageUnchecked();
+ }
+
+ // Called by AMS, must check permission
+ String getSwitchingToSystemUserMessage() {
+ checkHasManageUsersPermission("getSwitchingToSystemUserMessage()");
+
+ return getSwitchingToSystemUserMessageUnchecked();
+ }
+
+ private String getSwitchingFromSystemUserMessageUnchecked() {
synchronized (mLock) {
return mSwitchingFromSystemUserMessage;
}
}
- private String getSwitchingToSystemUserMessage() {
+ private String getSwitchingToSystemUserMessageUnchecked() {
synchronized (mLock) {
return mSwitchingToSystemUserMessage;
}
}
+ private void checkHasManageUsersPermission(String operation) {
+ if (mInjector.checkCallingPermission(
+ android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to call " + operation);
+ }
+ }
+
void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (mLock) {
long token = proto.start(fieldId);
@@ -2611,11 +2666,17 @@ class UserController implements Handler.Callback {
pw.println(" mTargetUserId:" + mTargetUserId);
pw.println(" mLastActiveUsers:" + mLastActiveUsers);
pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking);
- pw.println(" shouldStopBackgroundUsersOnSwitch:"
- + shouldStopBackgroundUsersOnSwitch());
+ pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch());
+ pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch);
pw.println(" mMaxRunningUsers:" + mMaxRunningUsers);
pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled);
pw.println(" mInitialized:" + mInitialized);
+ if (mSwitchingFromSystemUserMessage != null) {
+ pw.println(" mSwitchingFromSystemUserMessage: " + mSwitchingFromSystemUserMessage);
+ }
+ if (mSwitchingToSystemUserMessage != null) {
+ pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 1fe76080fc6f..40fc3066e2bb 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -145,4 +145,11 @@ public final class UserState {
proto.write(UserStateProto.SWITCHING, switching);
proto.end(token);
}
+
+ @Override
+ public String toString() {
+ return "[UserState: id=" + mHandle.getIdentifier() + ", state=" + stateToString(state)
+ + ", lastState=" + stateToString(lastState) + ", switching=" + switching
+ + ", tokenProvided=" + tokenProvided + "]";
+ }
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java b/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java
index b0335fe404f4..a3c9612e0e2c 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationManagerInternal.java
@@ -43,4 +43,9 @@ public abstract class AppHibernationManagerInternal {
* @see AppHibernationService#setHibernatingGlobally
*/
public abstract void setHibernatingGlobally(String packageName, boolean isHibernating);
+
+ /**
+ * @see AppHibernationService#isOatArtifactDeletionEnabled
+ */
+ public abstract boolean isOatArtifactDeletionEnabled();
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index 19dcee4828dd..4d025c981ce9 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -200,6 +200,14 @@ public final class AppHibernationService extends SystemService {
}
/**
+ * Whether global hibernation should delete ART ahead-of-time compilation artifacts and prevent
+ * package manager from re-optimizing the APK.
+ */
+ private boolean isOatArtifactDeletionEnabled() {
+ return mOatArtifactDeletionEnabled;
+ }
+
+ /**
* Whether a package is hibernating for a given user.
*
* @param packageName the package to check
@@ -269,16 +277,16 @@ public final class AppHibernationService extends SystemService {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
- userId = handleIncomingUser(userId, methodName);
- if (!checkUserStatesExist(userId, methodName)) {
+ final int realUserId = handleIncomingUser(userId, methodName);
+ if (!checkUserStatesExist(realUserId, methodName)) {
return;
}
synchronized (mLock) {
- final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
+ final Map<String, UserLevelState> packageStates = mUserStates.get(realUserId);
final UserLevelState pkgState = packageStates.get(packageName);
if (pkgState == null) {
Slog.e(TAG, String.format("Package %s is not installed for user %s",
- packageName, userId));
+ packageName, realUserId));
return;
}
@@ -286,13 +294,17 @@ public final class AppHibernationService extends SystemService {
return;
}
+ pkgState.hibernated = isHibernating;
if (isHibernating) {
- hibernatePackageForUser(packageName, userId, pkgState);
+ mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId));
} else {
- unhibernatePackageForUser(packageName, userId, pkgState);
+ mBackgroundExecutor.execute(
+ () -> unhibernatePackageForUser(packageName, realUserId));
+ pkgState.lastUnhibernatedMs = System.currentTimeMillis();
}
+
final UserLevelState stateSnapshot = new UserLevelState(pkgState);
- final int userIdSnapshot = userId;
+ final int userIdSnapshot = realUserId;
mBackgroundExecutor.execute(() -> {
FrameworkStatsLog.write(
FrameworkStatsLog.USER_LEVEL_HIBERNATION_STATE_CHANGED,
@@ -300,8 +312,8 @@ public final class AppHibernationService extends SystemService {
userIdSnapshot,
stateSnapshot.hibernated);
});
- List<UserLevelState> states = new ArrayList<>(mUserStates.get(userId).values());
- mUserDiskStores.get(userId).scheduleWriteHibernationStates(states);
+ List<UserLevelState> states = new ArrayList<>(mUserStates.get(realUserId).values());
+ mUserDiskStores.get(realUserId).scheduleWriteHibernationStates(states);
}
}
@@ -326,10 +338,12 @@ public final class AppHibernationService extends SystemService {
return;
}
if (state.hibernated != isHibernating) {
+ state.hibernated = isHibernating;
if (isHibernating) {
- hibernatePackageGlobally(packageName, state);
+ mBackgroundExecutor.execute(() -> hibernatePackageGlobally(packageName, state));
} else {
- unhibernatePackageGlobally(packageName, state);
+ state.savedByte = 0;
+ state.lastUnhibernatedMs = System.currentTimeMillis();
}
List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values());
mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states);
@@ -366,20 +380,16 @@ public final class AppHibernationService extends SystemService {
}
/**
- * Put an app into hibernation for a given user, allowing user-level optimizations to occur.
- *
- * @param pkgState package hibernation state
+ * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
+ * not hold {@link #mLock} while calling this to avoid deadlock scenarios.
*/
- @GuardedBy("mLock")
- private void hibernatePackageForUser(@NonNull String packageName, int userId,
- @NonNull UserLevelState pkgState) {
+ private void hibernatePackageForUser(@NonNull String packageName, int userId) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
final long caller = Binder.clearCallingIdentity();
try {
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
- pkgState.hibernated = true;
} catch (RemoteException e) {
throw new IllegalStateException(
"Failed to hibernate due to manager not being available", e);
@@ -390,16 +400,11 @@ public final class AppHibernationService extends SystemService {
}
/**
- * Remove a package from hibernation for a given user.
- *
- * @param pkgState package hibernation state
+ * Remove a package from hibernation for a given user. Do not hold {@link #mLock} while calling
+ * this.
*/
- @GuardedBy("mLock")
- private void unhibernatePackageForUser(@NonNull String packageName, int userId,
- UserLevelState pkgState) {
+ private void unhibernatePackageForUser(@NonNull String packageName, int userId) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
- pkgState.hibernated = false;
- pkgState.lastUnhibernatedMs = System.currentTimeMillis();
final long caller = Binder.clearCallingIdentity();
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
@@ -450,29 +455,20 @@ public final class AppHibernationService extends SystemService {
}
/**
- * Put a package into global hibernation, optimizing its storage at a package / APK level.
+ * Put a package into global hibernation, optimizing its storage at a package / APK level. Do
+ * not hold {@link #mLock} while calling this.
*/
- @GuardedBy("mLock")
private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
+ long savedBytes = 0;
if (mOatArtifactDeletionEnabled) {
- state.savedByte = Math.max(
+ savedBytes = Math.max(
mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName),
0);
}
- state.hibernated = true;
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- }
-
- /**
- * Unhibernate a package from global hibernation.
- */
- @GuardedBy("mLock")
- private void unhibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally");
- state.hibernated = false;
- state.savedByte = 0;
- state.lastUnhibernatedMs = System.currentTimeMillis();
+ synchronized (mLock) {
+ state.savedByte = savedBytes;
+ }
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
@@ -742,6 +738,11 @@ public final class AppHibernationService extends SystemService {
public boolean isHibernatingGlobally(String packageName) {
return mService.isHibernatingGlobally(packageName);
}
+
+ @Override
+ public boolean isOatArtifactDeletionEnabled() {
+ return mService.isOatArtifactDeletionEnabled();
+ }
}
private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 64b9bd98a2fc..6d29c379d1b1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1314,6 +1314,7 @@ public class AppOpsService extends IAppOpsService.Stub {
event.getAttributionFlags(), event.getAttributionChainId());
}
+ events = isRunning ? mInProgressEvents : mPausedInProgressEvents;
InProgressStartOpEvent newEvent = events.get(binders.get(i));
if (newEvent != null) {
newEvent.numUnfinishedStarts += numPreviousUnfinishedStarts - 1;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index e4e6a5424efa..3817ceaa1d15 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -328,7 +328,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
boolean isBtScoRequested = isBluetoothScoRequested();
- if (isBtScoRequested && !wasBtScoRequested) {
+ if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
+ pid);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 31bd596497cd..6eb3235566f9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -241,6 +241,9 @@ public class AudioDeviceInventory {
//------------------------------------------------------------
/*package*/ void dump(PrintWriter pw, String prefix) {
+ pw.println("\n" + prefix + "BECOMING_NOISY_INTENT_DEVICES_SET=");
+ BECOMING_NOISY_INTENT_DEVICES_SET.forEach(device -> {
+ pw.print(" 0x" + Integer.toHexString(device)); });
pw.println("\n" + prefix + "Preferred devices for strategy:");
mPreferredDevices.forEach((strategy, device) -> {
pw.println(" " + prefix + "strategy:" + strategy + " device:" + device); });
@@ -1254,10 +1257,13 @@ public class AudioDeviceInventory {
state == AudioService.CONNECTION_STATE_CONNECTED
? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED);
if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
+ Log.i(TAG, "not sending NOISY: state=" + state);
mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
return 0;
}
if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) {
+ Log.i(TAG, "not sending NOISY: device=0x" + Integer.toHexString(device)
+ + " not in set " + BECOMING_NOISY_INTENT_DEVICES_SET);
mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
return 0;
}
@@ -1267,18 +1273,24 @@ public class AudioDeviceInventory {
if (((di.mDeviceType & AudioSystem.DEVICE_BIT_IN) == 0)
&& BECOMING_NOISY_INTENT_DEVICES_SET.contains(di.mDeviceType)) {
devices.add(di.mDeviceType);
+ Log.i(TAG, "NOISY: adding 0x" + Integer.toHexString(di.mDeviceType));
}
}
if (musicDevice == AudioSystem.DEVICE_NONE) {
musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ Log.i(TAG, "NOISY: musicDevice changing from NONE to 0x"
+ + Integer.toHexString(musicDevice));
}
// always ignore condition on device being actually used for music when in communication
// because music routing is altered in this case.
// also checks whether media routing if affected by a dynamic policy or mirroring
- if (((device == musicDevice) || mDeviceBroker.isInCommunication())
- && AudioSystem.isSingleAudioDeviceType(devices, device)
- && !mDeviceBroker.hasMediaDynamicPolicy()
+ final boolean inCommunication = mDeviceBroker.isInCommunication();
+ final boolean singleAudioDeviceType = AudioSystem.isSingleAudioDeviceType(devices, device);
+ final boolean hasMediaDynamicPolicy = mDeviceBroker.hasMediaDynamicPolicy();
+ if (((device == musicDevice) || inCommunication)
+ && singleAudioDeviceType
+ && !hasMediaDynamicPolicy
&& (musicDevice != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX)) {
if (!mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0 /*not looking in past*/)
&& !mDeviceBroker.hasAudioFocusUsers()) {
@@ -1291,6 +1303,12 @@ public class AudioDeviceInventory {
}
mDeviceBroker.postBroadcastBecomingNoisy();
delay = SystemProperties.getInt("audio.sys.noisy.broadcast.delay", 700);
+ } else {
+ Log.i(TAG, "not sending NOISY: device:0x" + Integer.toHexString(device)
+ + " musicDevice:0x" + Integer.toHexString(musicDevice)
+ + " inComm:" + inCommunication
+ + " mediaPolicy:" + hasMediaDynamicPolicy
+ + " singleDevice:" + singleAudioDeviceType);
}
mmi.set(MediaMetrics.Property.DELAY_MS, delay).record();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c0b58998ddf1..c9bc6308ca8a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -93,11 +93,16 @@ import android.media.ICommunicationDeviceDispatcher;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
+import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.IVolumeController;
import android.media.MediaMetrics;
import android.media.MediaRecorder.AudioSource;
import android.media.PlayerBase;
+import android.media.Spatializer;
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
import android.media.audiopolicy.AudioMix;
@@ -198,7 +203,8 @@ import java.util.stream.Collectors;
*/
public class AudioService extends IAudioService.Stub
implements AccessibilityManager.TouchExplorationStateChangeListener,
- AccessibilityManager.AccessibilityServicesStateChangeListener {
+ AccessibilityManager.AccessibilityServicesStateChangeListener,
+ AudioSystemAdapter.OnRoutingUpdatedListener {
private static final String TAG = "AS.AudioService";
@@ -241,7 +247,7 @@ public class AudioService extends IAudioService.Stub
*/
private static final int FLAG_ADJUST_VOLUME = 1;
- private final Context mContext;
+ final Context mContext;
private final ContentResolver mContentResolver;
private final AppOpsManager mAppOps;
@@ -318,12 +324,16 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38;
private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 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;
+ private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
private static final int MSG_DISABLE_AUDIO_FOR_UID = 100;
private static final int MSG_INIT_STREAMS_VOLUMES = 101;
+ private static final int MSG_INIT_SPATIALIZER = 102;
// end of messages handled under wakelock
// retry delay in case of failure to indicate system ready to AudioFlinger
@@ -873,6 +883,8 @@ public class AudioService extends IAudioService.Stub
mSfxHelper = new SoundEffectsHelper(mContext);
+ mSpatializerHelper = new SpatializerHelper(this, mAudioSystem);
+
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator();
@@ -1034,6 +1046,8 @@ public class AudioService extends IAudioService.Stub
mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
+ mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false);
+
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
@@ -1046,6 +1060,9 @@ public class AudioService extends IAudioService.Stub
mCachedParams.put("facing", "none");
mCachedParams.put("hdr_audio_channel_count", "0");
mCachedParams.put("hdr_audio_sampling_rate", "0");
+
+ queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER,
+ 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
}
/**
@@ -1235,6 +1252,25 @@ public class AudioService extends IAudioService.Stub
updateVibratorInfos();
}
+ //-----------------------------------------------------------------
+ // routing monitoring from AudioSystemAdapter
+ @Override
+ public void onRoutingUpdatedFromNative() {
+ if (!mHasSpatializerEffect) {
+ return;
+ }
+ sendMsg(mAudioHandler,
+ MSG_ROUTING_UPDATED,
+ SENDMSG_REPLACE, 0, 0, null,
+ /*delay*/ 0);
+ }
+
+ void monitorRoutingChanges(boolean enabled) {
+ mAudioSystem.setRoutingListener(enabled ? this : null);
+ }
+
+
+ //-----------------------------------------------------------------
RoleObserver mRoleObserver;
class RoleObserver implements OnRoleHoldersChangedListener {
@@ -1439,6 +1475,11 @@ public class AudioService extends IAudioService.Stub
}
}
+ if (mHasSpatializerEffect) {
+ mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
+ monitorRoutingChanges(true);
+ }
+
onIndicateSystemReady();
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
@@ -7617,6 +7658,19 @@ public class AudioService extends IAudioService.Stub
mAudioEventWakeLock.release();
break;
+ case MSG_INIT_SPATIALIZER:
+ mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
+ if (mHasSpatializerEffect) {
+ mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
+ monitorRoutingChanges(true);
+ }
+ mAudioEventWakeLock.release();
+ break;
+
+ case MSG_INIT_HEADTRACKING_SENSORS:
+ mSpatializerHelper.onInitSensors();
+ break;
+
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive((String) msg.obj);
break;
@@ -7749,6 +7803,14 @@ public class AudioService extends IAudioService.Stub
case MSG_DISPATCH_AUDIO_MODE:
dispatchMode(msg.arg1);
break;
+
+ case MSG_ROUTING_UPDATED:
+ mSpatializerHelper.onRoutingUpdated();
+ break;
+
+ case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
+ onPersistSpatialAudioEnabled(msg.arg1 == 1);
+ break;
}
}
}
@@ -8316,6 +8378,239 @@ public class AudioService extends IAudioService.Stub
}
//==========================================================================================
+ private final @NonNull SpatializerHelper mSpatializerHelper;
+ /**
+ * Initialized from property ro.audio.spatializer_enabled
+ * Should only be 1 when the device ships with a Spatializer effect
+ */
+ private final boolean mHasSpatializerEffect;
+ /**
+ * Default value for the spatial audio feature
+ */
+ private static final boolean SPATIAL_AUDIO_ENABLED_DEFAULT = true;
+
+ /**
+ * persist in user settings whether the feature is enabled.
+ * Can change when {@link Spatializer#setEnabled(boolean)} is called and successfully
+ * changes the state of the feature
+ * @param featureEnabled
+ */
+ void persistSpatialAudioEnabled(boolean featureEnabled) {
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_SPATIAL_AUDIO_ENABLED,
+ SENDMSG_REPLACE, featureEnabled ? 1 : 0, 0, null,
+ /*delay ms*/ 100);
+ }
+
+ void onPersistSpatialAudioEnabled(boolean enabled) {
+ Settings.Secure.putIntForUser(mContentResolver,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
+ }
+
+ boolean isSpatialAudioEnabled() {
+ return Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.SPATIAL_AUDIO_ENABLED, SPATIAL_AUDIO_ENABLED_DEFAULT ? 1 : 0,
+ UserHandle.USER_CURRENT) == 1;
+ }
+
+ private void enforceModifyDefaultAudioEffectsPermission() {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing MODIFY_DEFAULT_AUDIO_EFFECTS permission");
+ }
+ }
+
+ /**
+ * Returns the immersive audio level that the platform is capable of
+ * @see Spatializer#getImmersiveAudioLevel()
+ */
+ public int getSpatializerImmersiveAudioLevel() {
+ return mSpatializerHelper.getCapableImmersiveAudioLevel();
+ }
+
+ /** @see Spatializer#isEnabled() */
+ public boolean isSpatializerEnabled() {
+ return mSpatializerHelper.isEnabled();
+ }
+
+ /** @see Spatializer#isAvailable() */
+ public boolean isSpatializerAvailable() {
+ return mSpatializerHelper.isAvailable();
+ }
+
+ /** @see Spatializer#setSpatializerEnabled(boolean) */
+ public void setSpatializerEnabled(boolean enabled) {
+ enforceModifyDefaultAudioEffectsPermission();
+ mSpatializerHelper.setFeatureEnabled(enabled);
+ }
+
+ /** @see Spatializer#canBeSpatialized() */
+ public boolean canBeSpatialized(
+ @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
+ Objects.requireNonNull(attributes);
+ Objects.requireNonNull(format);
+ return mSpatializerHelper.canBeSpatialized(attributes, format);
+ }
+
+ /** @see Spatializer.SpatializerInfoDispatcherStub */
+ public void registerSpatializerCallback(
+ @NonNull ISpatializerCallback cb) {
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerStateCallback(cb);
+ }
+
+ /** @see Spatializer.SpatializerInfoDispatcherStub */
+ public void unregisterSpatializerCallback(
+ @NonNull ISpatializerCallback cb) {
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterStateCallback(cb);
+ }
+
+ /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
+ public void registerSpatializerHeadTrackingCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerHeadTrackingModeCallback(cb);
+ }
+
+ /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */
+ public void unregisterSpatializerHeadTrackingCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterHeadTrackingModeCallback(cb);
+ }
+
+ /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */
+ public void registerHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */
+ public void unregisterHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb);
+ }
+
+ /** @see Spatializer#getSpatializerCompatibleAudioDevices() */
+ public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getCompatibleAudioDevices();
+ }
+
+ /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
+ public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(ada);
+ mSpatializerHelper.addCompatibleAudioDevice(ada);
+ }
+
+ /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */
+ public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(ada);
+ mSpatializerHelper.removeCompatibleAudioDevice(ada);
+ }
+
+ /** @see Spatializer#getSupportedHeadTrackingModes() */
+ public int[] getSupportedHeadTrackingModes() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getSupportedHeadTrackingModes();
+ }
+
+ /** @see Spatializer#getHeadTrackingMode() */
+ public int getActualHeadTrackingMode() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getActualHeadTrackingMode();
+ }
+
+ /** @see Spatializer#getDesiredHeadTrackingMode() */
+ public int getDesiredHeadTrackingMode() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getDesiredHeadTrackingMode();
+ }
+
+ /** @see Spatializer#setGlobalTransform */
+ public void setSpatializerGlobalTransform(@NonNull float[] transform) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(transform);
+ mSpatializerHelper.setGlobalTransform(transform);
+ }
+
+ /** @see Spatializer#recenterHeadTracker() */
+ public void recenterHeadTracker() {
+ enforceModifyDefaultAudioEffectsPermission();
+ mSpatializerHelper.recenterHeadTracker();
+ }
+
+ /** @see Spatializer#setDesiredHeadTrackingMode */
+ public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+ enforceModifyDefaultAudioEffectsPermission();
+ switch(mode) {
+ case Spatializer.HEAD_TRACKING_MODE_DISABLED:
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
+ break;
+ default:
+ return;
+ }
+ mSpatializerHelper.setDesiredHeadTrackingMode(mode);
+ }
+
+ /** @see Spatializer#setEffectParameter */
+ public void setSpatializerParameter(int key, @NonNull byte[] value) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(value);
+ mSpatializerHelper.setEffectParameter(key, value);
+ }
+
+ /** @see Spatializer#getEffectParameter */
+ public void getSpatializerParameter(int key, @NonNull byte[] value) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(value);
+ mSpatializerHelper.getEffectParameter(key, value);
+ }
+
+ /** @see Spatializer#getOutput */
+ public int getSpatializerOutput() {
+ enforceModifyDefaultAudioEffectsPermission();
+ return mSpatializerHelper.getOutput();
+ }
+
+ /** @see Spatializer#setOnSpatializerOutputChangedListener */
+ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.registerSpatializerOutputCallback(cb);
+ }
+
+ /** @see Spatializer#clearOnSpatializerOutputChangedListener */
+ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) {
+ enforceModifyDefaultAudioEffectsPermission();
+ Objects.requireNonNull(cb);
+ mSpatializerHelper.unregisterSpatializerOutputCallback(cb);
+ }
+
+ /**
+ * post a message to schedule init/release of head tracking sensors
+ * whether to initialize or release sensors is based on the state of spatializer
+ */
+ void postInitSpatializerHeadTrackingSensors() {
+ sendMsg(mAudioHandler,
+ MSG_INIT_HEADTRACKING_SENSORS,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
+ }
+
+ //==========================================================================================
private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) ||
mContext.getResources().getBoolean(
@@ -8820,8 +9115,6 @@ public class AudioService extends IAudioService.Stub
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- mAudioSystem.dump(pw);
-
sLifecycleLogger.dump(pw);
if (mAudioHandler != null) {
pw.println("\nMessage handler (watch for unhandled messages):");
@@ -8896,6 +9189,14 @@ public class AudioService extends IAudioService.Stub
sVolumeLogger.dump(pw);
pw.println("\n");
dumpSupportedSystemUsage(pw);
+
+ pw.println("\n");
+ pw.println("\nSpatial audio:");
+ pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect);
+ pw.println("isSpatializerEnabled:" + isSpatializerEnabled());
+ pw.println("isSpatialAudioEnabled:" + isSpatialAudioEnabled());
+
+ mAudioSystem.dump(pw);
}
private void dumpSupportedSystemUsage(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 6d567807f357..a2ba76b6fd6a 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -17,6 +17,7 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioSystem;
@@ -24,6 +25,8 @@ import android.media.audiopolicy.AudioMix;
import android.os.SystemClock;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +62,9 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback {
private ConcurrentHashMap<AudioAttributes, ArrayList<AudioDeviceAttributes>>
mDevicesForAttrCache;
private int[] mMethodCacheHit;
+ private static final Object sRoutingListenerLock = new Object();
+ @GuardedBy("sRoutingListenerLock")
+ private static @Nullable OnRoutingUpdatedListener sRoutingListener;
/**
* should be false except when trying to debug caching errors. When true, the value retrieved
@@ -76,6 +82,23 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback {
Log.d(TAG, "---- onRoutingUpdated (from native) ----------");
}
invalidateRoutingCache();
+ final OnRoutingUpdatedListener listener;
+ synchronized (sRoutingListenerLock) {
+ listener = sRoutingListener;
+ }
+ if (listener != null) {
+ listener.onRoutingUpdatedFromNative();
+ }
+ }
+
+ interface OnRoutingUpdatedListener {
+ void onRoutingUpdatedFromNative();
+ }
+
+ static void setRoutingListener(@Nullable OnRoutingUpdatedListener listener) {
+ synchronized (sRoutingListenerLock) {
+ sRoutingListener = listener;
+ }
}
/**
@@ -501,11 +524,28 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback {
* @param pw
*/
public void dump(PrintWriter pw) {
+ pw.println("\nAudioSystemAdapter:");
+ pw.println(" mDevicesForStreamCache:");
+ if (mDevicesForStreamCache != null) {
+ for (Integer stream : mDevicesForStreamCache.keySet()) {
+ pw.println("\t stream:" + stream + " device:"
+ + AudioSystem.getOutputDeviceName(mDevicesForStreamCache.get(stream)));
+ }
+ }
+ pw.println(" mDevicesForAttrCache:");
+ if (mDevicesForAttrCache != null) {
+ for (AudioAttributes attr : mDevicesForAttrCache.keySet()) {
+ pw.println("\t" + attr);
+ for (AudioDeviceAttributes devAttr : mDevicesForAttrCache.get(attr)) {
+ pw.println("\t\t" + devAttr);
+ }
+ }
+ }
+
if (!ENABLE_GETDEVICES_STATS) {
- // only stats in this dump
+ // only stats in the rest of this dump
return;
}
- pw.println("\nAudioSystemAdapter:");
for (int i = 0; i < NB_MEASUREMENTS; i++) {
pw.println(mMethodNames[i]
+ ": counter=" + mMethodCallCounter[i]
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index ad7216600e61..d4f4245f19c5 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -17,33 +17,44 @@
package com.android.server.audio;
import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManager;
import android.media.AudioSystem;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.util.Log;
import android.view.Surface;
import android.view.WindowManager;
/**
* Class to handle device rotation events for AudioService, and forward device rotation
- * to the audio HALs through AudioSystem.
+ * and folded state to the audio HALs through AudioSystem.
*
* The role of this class is to monitor device orientation changes, and upon rotation,
* verify the UI orientation. In case of a change, send the new orientation, in increments
* of 90deg, through AudioSystem.
*
+ * Another role of this class is to track device folded state changes. In case of a
+ * change, send the new folded state through AudioSystem.
+ *
* Note that even though we're responding to device orientation events, we always
* query the display rotation so audio stays in sync with video/dialogs. This is
* done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE.
+ *
+ * We also monitor current display ID and audio is able to know which display is active.
*/
class RotationHelper {
private static final String TAG = "AudioService.RotationHelper";
private static AudioDisplayListener sDisplayListener;
+ private static FoldStateListener sFoldStateListener;
private static final Object sRotationLock = new Object();
+ private static final Object sFoldStateLock = new Object();
private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock
+ private static boolean sDeviceFold = true; // R/W synchronized on sFoldStateLock
private static Context sContext;
private static Handler sHandler;
@@ -67,11 +78,17 @@ class RotationHelper {
((DisplayManager) sContext.getSystemService(Context.DISPLAY_SERVICE))
.registerDisplayListener(sDisplayListener, sHandler);
updateOrientation();
+
+ sFoldStateListener = new FoldStateListener(sContext, folded -> updateFoldState(folded));
+ sContext.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(sHandler), sFoldStateListener);
}
static void disable() {
((DisplayManager) sContext.getSystemService(Context.DISPLAY_SERVICE))
.unregisterDisplayListener(sDisplayListener);
+ sContext.getSystemService(DeviceStateManager.class)
+ .unregisterCallback(sFoldStateListener);
}
/**
@@ -112,6 +129,22 @@ class RotationHelper {
}
/**
+ * publish the change of device folded state if any.
+ */
+ static void updateFoldState(boolean newFolded) {
+ synchronized (sFoldStateLock) {
+ if (sDeviceFold != newFolded) {
+ sDeviceFold = newFolded;
+ if (newFolded) {
+ AudioSystem.setParameters("device_folded=on");
+ } else {
+ AudioSystem.setParameters("device_folded=off");
+ }
+ }
+ }
+ }
+
+ /**
* Uses android.hardware.display.DisplayManager.DisplayListener
*/
final static class AudioDisplayListener implements DisplayManager.DisplayListener {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
new file mode 100644
index 000000000000..b47ea4f7a4b8
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -0,0 +1,986 @@
+/*
+ * 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.audio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioFormat;
+import android.media.AudioSystem;
+import android.media.INativeSpatializerCallback;
+import android.media.ISpatializer;
+import android.media.ISpatializerCallback;
+import android.media.ISpatializerHeadToSoundStagePoseCallback;
+import android.media.ISpatializerHeadTrackingCallback;
+import android.media.ISpatializerHeadTrackingModeCallback;
+import android.media.ISpatializerOutputCallback;
+import android.media.SpatializationLevel;
+import android.media.Spatializer;
+import android.media.SpatializerHeadTrackingMode;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * A helper class to manage Spatializer related functionality
+ */
+public class SpatializerHelper {
+
+ private static final String TAG = "AS.SpatializerHelper";
+ private static final boolean DEBUG = true;
+ private static final boolean DEBUG_MORE = false;
+
+ private static void logd(String s) {
+ if (DEBUG) {
+ Log.i(TAG, s);
+ }
+ }
+
+ private final @NonNull AudioSystemAdapter mASA;
+ private final @NonNull AudioService mAudioService;
+ private @Nullable SensorManager mSensorManager;
+
+ //------------------------------------------------------------
+ /** head tracker sensor name */
+ // TODO: replace with generic head tracker sensor name.
+ // the current implementation refers to the "google" namespace but will be replaced
+ // by an android name at the next API level revision, it is not Google-specific.
+ // Also see "TODO-HT" in onInitSensors() method
+ private static final String HEADTRACKER_SENSOR =
+ "com.google.hardware.sensor.hid_dynamic.headtracker";
+
+ // Spatializer state machine
+ private static final int STATE_UNINITIALIZED = 0;
+ private static final int STATE_NOT_SUPPORTED = 1;
+ private static final int STATE_DISABLED_UNAVAILABLE = 3;
+ private static final int STATE_ENABLED_UNAVAILABLE = 4;
+ private static final int STATE_ENABLED_AVAILABLE = 5;
+ private static final int STATE_DISABLED_AVAILABLE = 6;
+ private int mState = STATE_UNINITIALIZED;
+
+ /** 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;
+ private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ private int mSpatOutput = 0;
+ private @Nullable ISpatializer mSpat;
+ private @Nullable SpatializerCallback mSpatCallback;
+ private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
+ private @Nullable HelperDynamicSensorCallback mDynSensorCallback;
+
+ // default attributes and format that determine basic availability of spatialization
+ private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+ private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(48000)
+ .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
+ .build();
+ // device array to store the routing for the default attributes and format, size 1 because
+ // media is never expected to be duplicated
+ private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1];
+
+ //---------------------------------------------------------------
+ // audio device compatibility / enabled
+
+ private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0);
+
+ //------------------------------------------------------
+ // initialization
+ SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) {
+ mAudioService = mother;
+ mASA = asa;
+ }
+
+ synchronized void init(boolean effectExpected) {
+ Log.i(TAG, "Initializing");
+ if (!effectExpected) {
+ Log.i(TAG, "Setting state to STATE_NOT_SUPPORTED due to effect not expected");
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ if (mState != STATE_UNINITIALIZED) {
+ throw new IllegalStateException(("init() called in state:" + mState));
+ }
+ // is there a spatializer?
+ mSpatCallback = new SpatializerCallback();
+ final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback);
+ if (spat == null) {
+ Log.i(TAG, "init(): No Spatializer found");
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ // capabilities of spatializer?
+ try {
+ byte[] levels = spat.getSupportedLevels();
+ if (levels == null
+ || levels.length == 0
+ || (levels.length == 1
+ && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) {
+ Log.e(TAG, "Spatializer is useless");
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ for (byte level : levels) {
+ logd("found support for level: " + level);
+ if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) {
+ logd("Setting capable level to LEVEL_MULTICHANNEL");
+ mCapableSpatLevel = level;
+ break;
+ }
+ }
+ } catch (RemoteException e) {
+ /* capable level remains at NONE*/
+ } finally {
+ if (spat != null) {
+ try {
+ spat.release();
+ } catch (RemoteException e) { /* capable level remains at NONE*/ }
+ }
+ }
+ if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+ mState = STATE_NOT_SUPPORTED;
+ return;
+ }
+ mState = STATE_DISABLED_UNAVAILABLE;
+ // note at this point mSpat is still not instantiated
+ }
+
+ /**
+ * Like init() but resets the state and spatializer levels
+ * @param featureEnabled
+ */
+ synchronized void reset(boolean featureEnabled) {
+ Log.i(TAG, "Resetting");
+ mState = STATE_UNINITIALIZED;
+ mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ init(true);
+ setFeatureEnabled(featureEnabled);
+ }
+
+ //------------------------------------------------------
+ // routing monitoring
+ void onRoutingUpdated() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ return;
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ break;
+ }
+ mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
+ final boolean able =
+ AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
+ logd("onRoutingUpdated: can spatialize media 5.1:" + able
+ + " on device:" + ROUTING_DEVICES[0]);
+ setDispatchAvailableState(able);
+ }
+
+ //------------------------------------------------------
+ // spatializer callback from native
+ private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
+
+ public void onLevelChanged(byte level) {
+ logd("SpatializerCallback.onLevelChanged level:" + level);
+ synchronized (SpatializerHelper.this) {
+ mSpatLevel = spatializationLevelToSpatializerInt(level);
+ }
+ // TODO use reported spat level to change state
+
+ // init sensors
+ postInitSensors();
+ }
+
+ public void onOutputChanged(int output) {
+ logd("SpatializerCallback.onOutputChanged output:" + output);
+ int oldOutput;
+ synchronized (SpatializerHelper.this) {
+ oldOutput = mSpatOutput;
+ mSpatOutput = output;
+ }
+ if (oldOutput != output) {
+ dispatchOutputUpdate(output);
+ }
+ }
+ };
+
+ //------------------------------------------------------
+ // spatializer head tracking callback from native
+ private final class SpatializerHeadTrackingCallback
+ extends ISpatializerHeadTrackingCallback.Stub {
+ public void onHeadTrackingModeChanged(byte mode) {
+ logd("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" + mode);
+ int oldMode, newMode;
+ synchronized (this) {
+ oldMode = mActualHeadTrackingMode;
+ mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode);
+ newMode = mActualHeadTrackingMode;
+ }
+ if (oldMode != newMode) {
+ dispatchActualHeadTrackingMode(newMode);
+ }
+ }
+
+ public void onHeadToSoundStagePoseUpdated(float[] headToStage) {
+ if (headToStage == null) {
+ Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
+ + "null transform");
+ return;
+ }
+ if (headToStage.length != 6) {
+ Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
+ + " invalid transform length" + headToStage.length);
+ return;
+ }
+ if (DEBUG_MORE) {
+ // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
+ StringBuilder t = new StringBuilder(42);
+ for (float val : headToStage) {
+ t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]");
+ }
+ logd("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" + t);
+ }
+ dispatchPoseUpdate(headToStage);
+ }
+ };
+
+ //------------------------------------------------------
+ // dynamic sensor callback
+ private final class HelperDynamicSensorCallback extends SensorManager.DynamicSensorCallback {
+ @Override
+ public void onDynamicSensorConnected(Sensor sensor) {
+ postInitSensors();
+ }
+
+ @Override
+ public void onDynamicSensorDisconnected(Sensor sensor) {
+ postInitSensors();
+ }
+ }
+
+ //------------------------------------------------------
+ // compatible devices
+ /**
+ * @return a shallow copy of the list of compatible audio devices
+ */
+ synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
+ return (List<AudioDeviceAttributes>) mCompatibleAudioDevices.clone();
+ }
+
+ synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ if (!mCompatibleAudioDevices.contains(ada)) {
+ mCompatibleAudioDevices.add(ada);
+ }
+ }
+
+ synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
+ mCompatibleAudioDevices.remove(ada);
+ }
+
+ //------------------------------------------------------
+ // states
+
+ synchronized boolean isEnabled() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ return false;
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ default:
+ return true;
+ }
+ }
+
+ synchronized boolean isAvailable() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ return false;
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ default:
+ return true;
+ }
+ }
+
+ synchronized void setFeatureEnabled(boolean enabled) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ if (enabled) {
+ throw(new IllegalStateException("Can't enable when uninitialized"));
+ }
+ return;
+ case STATE_NOT_SUPPORTED:
+ if (enabled) {
+ Log.e(TAG, "Can't enable when unsupported");
+ }
+ return;
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ if (enabled) {
+ createSpat();
+ break;
+ } else {
+ // already in disabled state
+ return;
+ }
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (!enabled) {
+ releaseSpat();
+ break;
+ } else {
+ // already in enabled state
+ return;
+ }
+ }
+ setDispatchFeatureEnabledState(enabled);
+ }
+
+ synchronized int getCapableImmersiveAudioLevel() {
+ return mCapableSpatLevel;
+ }
+
+ final RemoteCallbackList<ISpatializerCallback> mStateCallbacks =
+ new RemoteCallbackList<ISpatializerCallback>();
+
+ synchronized void registerStateCallback(
+ @NonNull ISpatializerCallback callback) {
+ mStateCallbacks.register(callback);
+ }
+
+ synchronized void unregisterStateCallback(
+ @NonNull ISpatializerCallback callback) {
+ mStateCallbacks.unregister(callback);
+ }
+
+ /**
+ * precondition: mState = STATE_*
+ * isFeatureEnabled() != featureEnabled
+ * @param featureEnabled
+ */
+ private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) {
+ if (featureEnabled) {
+ switch (mState) {
+ case STATE_DISABLED_UNAVAILABLE:
+ mState = STATE_ENABLED_UNAVAILABLE;
+ break;
+ case STATE_DISABLED_AVAILABLE:
+ mState = STATE_ENABLED_AVAILABLE;
+ break;
+ default:
+ throw(new IllegalStateException("Invalid mState:" + mState
+ + " for enabled true"));
+ }
+ } else {
+ switch (mState) {
+ case STATE_ENABLED_UNAVAILABLE:
+ mState = STATE_DISABLED_UNAVAILABLE;
+ break;
+ case STATE_ENABLED_AVAILABLE:
+ mState = STATE_DISABLED_AVAILABLE;
+ break;
+ default:
+ throw (new IllegalStateException("Invalid mState:" + mState
+ + " for enabled false"));
+ }
+ }
+ final int nbCallbacks = mStateCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mStateCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerEnabledChanged(featureEnabled);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
+ }
+ }
+ mStateCallbacks.finishBroadcast();
+ mAudioService.persistSpatialAudioEnabled(featureEnabled);
+ }
+
+ private synchronized void setDispatchAvailableState(boolean available) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw(new IllegalStateException(
+ "Should not update available state in state:" + mState));
+ case STATE_DISABLED_UNAVAILABLE:
+ if (available) {
+ mState = STATE_DISABLED_AVAILABLE;
+ break;
+ } else {
+ // already in unavailable state
+ return;
+ }
+ case STATE_ENABLED_UNAVAILABLE:
+ if (available) {
+ mState = STATE_ENABLED_AVAILABLE;
+ break;
+ } else {
+ // already in unavailable state
+ return;
+ }
+ case STATE_DISABLED_AVAILABLE:
+ if (available) {
+ // already in available state
+ return;
+ } else {
+ mState = STATE_DISABLED_UNAVAILABLE;
+ break;
+ }
+ case STATE_ENABLED_AVAILABLE:
+ if (available) {
+ // already in available state
+ return;
+ } else {
+ mState = STATE_ENABLED_UNAVAILABLE;
+ break;
+ }
+ }
+ final int nbCallbacks = mStateCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mStateCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerAvailableChanged(available);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
+ }
+ }
+ mStateCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
+ // native Spatializer management
+
+ /**
+ * precondition: mState == STATE_DISABLED_*
+ */
+ private void createSpat() {
+ if (mSpat == null) {
+ mSpatCallback = new SpatializerCallback();
+ mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback();
+ mSpat = AudioSystem.getSpatializer(mSpatCallback);
+ try {
+ mSpat.setLevel((byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
+ //TODO: register heatracking callback only when sensors are registered
+ if (mSpat.isHeadTrackingSupported()) {
+ mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't set spatializer level", e);
+ mState = STATE_NOT_SUPPORTED;
+ mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ }
+ }
+ }
+
+ /**
+ * precondition: mState == STATE_ENABLED_*
+ */
+ private void releaseSpat() {
+ if (mSpat != null) {
+ mSpatCallback = null;
+ try {
+ mSpat.registerHeadTrackingCallback(null);
+ mSpat.release();
+ mSpat = null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't set release spatializer cleanly", e);
+ }
+ }
+ }
+
+ //------------------------------------------------------
+ // virtualization capabilities
+ synchronized boolean canBeSpatialized(
+ @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
+ logd("canBeSpatialized usage:" + attributes.getUsage()
+ + " format:" + format.toLogFriendlyString());
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ logd("canBeSpatialized false due to state:" + mState);
+ return false;
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ break;
+ }
+
+ // filter on AudioAttributes usage
+ switch (attributes.getUsage()) {
+ case AudioAttributes.USAGE_MEDIA:
+ case AudioAttributes.USAGE_GAME:
+ break;
+ default:
+ logd("canBeSpatialized false due to usage:" + attributes.getUsage());
+ return false;
+ }
+ AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+ // going through adapter to take advantage of routing cache
+ mASA.getDevicesForAttributes(attributes).toArray(devices);
+ final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
+ logd("canBeSpatialized returning " + able);
+ return able;
+ }
+
+ //------------------------------------------------------
+ // head tracking
+ final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks =
+ new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>();
+
+ synchronized void registerHeadTrackingModeCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback callback) {
+ mHeadTrackingModeCallbacks.register(callback);
+ }
+
+ synchronized void unregisterHeadTrackingModeCallback(
+ @NonNull ISpatializerHeadTrackingModeCallback callback) {
+ mHeadTrackingModeCallbacks.unregister(callback);
+ }
+
+ synchronized int[] getSupportedHeadTrackingModes() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ return new int[0];
+ case STATE_NOT_SUPPORTED:
+ // return an empty list when Spatializer functionality is not supported
+ // because the list of head tracking modes you can set is actually empty
+ // as defined in {@link Spatializer#getSupportedHeadTrackingModes()}
+ return new int[0];
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ return new int[0];
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ final byte[] values = mSpat.getSupportedHeadTrackingModes();
+ ArrayList<Integer> list = new ArrayList<>(0);
+ for (byte value : values) {
+ switch (value) {
+ case SpatializerHeadTrackingMode.OTHER:
+ case SpatializerHeadTrackingMode.DISABLED:
+ // not expected here, skip
+ break;
+ case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ list.add(headTrackingModeTypeToSpatializerInt(value));
+ break;
+ default:
+ Log.e(TAG, "Unexpected head tracking mode:" + value,
+ new IllegalArgumentException("invalid mode"));
+ break;
+ }
+ }
+ int[] modes = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ modes[i] = list.get(i);
+ }
+ return modes;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e);
+ return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED };
+ }
+ }
+
+ synchronized int getActualHeadTrackingMode() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ case STATE_NOT_SUPPORTED:
+ return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
+ return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
+ }
+ }
+
+ synchronized int getDesiredHeadTrackingMode() {
+ return mDesiredHeadTrackingMode;
+ }
+
+ synchronized void setGlobalTransform(@NonNull float[] transform) {
+ if (transform.length != 6) {
+ throw new IllegalArgumentException("invalid array size" + transform.length);
+ }
+ if (!checkSpatForHeadTracking("setGlobalTransform")) {
+ return;
+ }
+ try {
+ mSpat.setGlobalTransform(transform);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setGlobalTransform", e);
+ }
+ }
+
+ synchronized void recenterHeadTracker() {
+ if (!checkSpatForHeadTracking("recenterHeadTracker")) {
+ return;
+ }
+ try {
+ mSpat.recenterHeadTracker();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling recenterHeadTracker", e);
+ }
+ }
+
+ synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
+ if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) {
+ return;
+ }
+ try {
+ if (mode != mDesiredHeadTrackingMode) {
+ mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode));
+ mDesiredHeadTrackingMode = mode;
+ dispatchDesiredHeadTrackingMode(mode);
+ }
+
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e);
+ }
+ }
+
+ private boolean checkSpatForHeadTracking(String funcName) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ return false;
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer when calling " + funcName));
+ }
+ break;
+ }
+ return true;
+ }
+
+ private void dispatchActualHeadTrackingMode(int newMode) {
+ final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mHeadTrackingModeCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerActualHeadTrackingModeChanged(newMode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e);
+ }
+ }
+ mHeadTrackingModeCallbacks.finishBroadcast();
+ }
+
+ private void dispatchDesiredHeadTrackingMode(int newMode) {
+ final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mHeadTrackingModeCallbacks.getBroadcastItem(i)
+ .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e);
+ }
+ }
+ mHeadTrackingModeCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
+ // head pose
+ final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks =
+ new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>();
+
+ synchronized void registerHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
+ mHeadPoseCallbacks.register(callback);
+ }
+
+ synchronized void unregisterHeadToSoundstagePoseCallback(
+ @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
+ mHeadPoseCallbacks.unregister(callback);
+ }
+
+ private void dispatchPoseUpdate(float[] pose) {
+ final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mHeadPoseCallbacks.getBroadcastItem(i)
+ .dispatchPoseChanged(pose);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchPoseChanged", e);
+ }
+ }
+ mHeadPoseCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
+ // vendor parameters
+ synchronized void setEffectParameter(int key, @NonNull byte[] value) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't set parameter key:" + key + " without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for setParameter for key:" + key));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ mSpat.setParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setParameter for key:" + key, e);
+ }
+ }
+
+ synchronized void getEffectParameter(int key, @NonNull byte[] value) {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get parameter key:" + key + " without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getParameter for key:" + key));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ mSpat.getParameter(key, value);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getParameter for key:" + key, e);
+ }
+ }
+
+ //------------------------------------------------------
+ // output
+
+ /** @see Spatializer#getOutput */
+ synchronized int getOutput() {
+ switch (mState) {
+ case STATE_UNINITIALIZED:
+ case STATE_NOT_SUPPORTED:
+ throw (new IllegalStateException(
+ "Can't get output without a spatializer"));
+ case STATE_ENABLED_UNAVAILABLE:
+ case STATE_DISABLED_UNAVAILABLE:
+ case STATE_DISABLED_AVAILABLE:
+ case STATE_ENABLED_AVAILABLE:
+ if (mSpat == null) {
+ throw (new IllegalStateException(
+ "null Spatializer for getOutput"));
+ }
+ break;
+ }
+ // mSpat != null
+ try {
+ return mSpat.getOutput();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in getOutput", e);
+ return 0;
+ }
+ }
+
+ final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks =
+ new RemoteCallbackList<ISpatializerOutputCallback>();
+
+ synchronized void registerSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.register(callback);
+ }
+
+ synchronized void unregisterSpatializerOutputCallback(
+ @NonNull ISpatializerOutputCallback callback) {
+ mOutputCallbacks.unregister(callback);
+ }
+
+ private void dispatchOutputUpdate(int output) {
+ final int nbCallbacks = mOutputCallbacks.beginBroadcast();
+ for (int i = 0; i < nbCallbacks; i++) {
+ try {
+ mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in dispatchOutputUpdate", e);
+ }
+ }
+ mOutputCallbacks.finishBroadcast();
+ }
+
+ //------------------------------------------------------
+ // sensors
+ private void postInitSensors() {
+ mAudioService.postInitSpatializerHeadTrackingSensors();
+ }
+
+ synchronized void onInitSensors() {
+ final boolean init = (mSpatLevel != SpatializationLevel.NONE);
+ final String action = init ? "initializing" : "releasing";
+ if (mSpat == null) {
+ Log.e(TAG, "not " + action + " sensors, null spatializer");
+ return;
+ }
+ try {
+ if (!mSpat.isHeadTrackingSupported()) {
+ Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking");
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "not " + action + " sensors, error querying headtracking", e);
+ return;
+ }
+ int headHandle = -1;
+ int screenHandle = -1;
+ if (init) {
+ if (mSensorManager == null) {
+ try {
+ mSensorManager = (SensorManager)
+ mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE);
+ mDynSensorCallback = new HelperDynamicSensorCallback();
+ mSensorManager.registerDynamicSensorCallback(mDynSensorCallback);
+ } catch (Exception e) {
+ Log.e(TAG, "Error with SensorManager, can't initialize sensors", e);
+ mSensorManager = null;
+ mDynSensorCallback = null;
+ return;
+ }
+ }
+ // initialize sensor handles
+ // TODO-HT update to non-private sensor once head tracker sensor is defined
+ for (Sensor sensor : mSensorManager.getDynamicSensorList(
+ Sensor.TYPE_DEVICE_PRIVATE_BASE)) {
+ if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
+ headHandle = sensor.getHandle();
+ break;
+ }
+ }
+ Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ screenHandle = screenSensor.getHandle();
+ } else {
+ if (mSensorManager != null && mDynSensorCallback != null) {
+ mSensorManager.unregisterDynamicSensorCallback(mDynSensorCallback);
+ mSensorManager = null;
+ mDynSensorCallback = null;
+ }
+ // -1 is disable value for both screen and head tracker handles
+ }
+ try {
+ Log.i(TAG, "setScreenSensor:" + screenHandle);
+ mSpat.setScreenSensor(screenHandle);
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e);
+ }
+ try {
+ Log.i(TAG, "setHeadSensor:" + headHandle);
+ mSpat.setHeadSensor(headHandle);
+ } catch (Exception e) {
+ Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e);
+ }
+ }
+
+ //------------------------------------------------------
+ // SDK <-> AIDL converters
+ private static int headTrackingModeTypeToSpatializerInt(byte mode) {
+ switch (mode) {
+ case SpatializerHeadTrackingMode.OTHER:
+ return Spatializer.HEAD_TRACKING_MODE_OTHER;
+ case SpatializerHeadTrackingMode.DISABLED:
+ return Spatializer.HEAD_TRACKING_MODE_DISABLED;
+ case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
+ case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
+ default:
+ throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode));
+ }
+ }
+
+ private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
+ switch (sdkMode) {
+ case Spatializer.HEAD_TRACKING_MODE_OTHER:
+ return SpatializerHeadTrackingMode.OTHER;
+ case Spatializer.HEAD_TRACKING_MODE_DISABLED:
+ return SpatializerHeadTrackingMode.DISABLED;
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
+ return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+ case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
+ return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+ default:
+ throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
+ }
+ }
+
+ private static int spatializationLevelToSpatializerInt(byte level) {
+ switch (level) {
+ case SpatializationLevel.NONE:
+ return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
+ case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+ return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
+ case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+ return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
+ default:
+ throw(new IllegalArgumentException("Unexpected spatializer level:" + level));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index b42f8980d1c0..9c8ccd946b7f 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -47,6 +47,7 @@ import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -766,8 +767,9 @@ public class AuthService extends SystemService {
if (isUdfps && udfpsProps.length == 3) {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
- componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, udfpsProps[0],
- udfpsProps[1], udfpsProps[2]);
+ componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken,
+ List.of(new SensorLocationInternal("" /* display */,
+ udfpsProps[0], udfpsProps[1], udfpsProps[2])));
} else {
return new FingerprintSensorPropertiesInternal(sensorId,
Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser,
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e0775d48b42f..758cf7a7d430 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1036,7 +1036,8 @@ public class BiometricService extends SystemService {
promptInfo.setAuthenticators(authenticators);
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
- userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */);
+ userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
+ getContext());
}
/**
@@ -1375,15 +1376,20 @@ public class BiometricService extends SystemService {
try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
- opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists());
+ opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
+ getContext());
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
+ "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
- + " requestId: " + requestId);
-
- if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
+ + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: "
+ + promptInfo.isIgnoreEnrollmentState());
+ // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
+ // be shown for this case.
+ if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
+ || preAuthStatus.second
+ == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index cd0ff10168bb..05c3f68f355b 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -26,6 +26,8 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -59,6 +61,7 @@ class PreAuthInfo {
static final int CREDENTIAL_NOT_ENROLLED = 9;
static final int BIOMETRIC_LOCKOUT_TIMED = 10;
static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
+ static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
@IntDef({AUTHENTICATOR_OK,
BIOMETRIC_NO_HARDWARE,
BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
@@ -69,7 +72,8 @@ class PreAuthInfo {
BIOMETRIC_NOT_ENABLED_FOR_APPS,
CREDENTIAL_NOT_ENROLLED,
BIOMETRIC_LOCKOUT_TIMED,
- BIOMETRIC_LOCKOUT_PERMANENT})
+ BIOMETRIC_LOCKOUT_PERMANENT,
+ BIOMETRIC_SENSOR_PRIVACY_ENABLED})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticatorStatus {}
@@ -83,13 +87,16 @@ class PreAuthInfo {
final List<Pair<BiometricSensor, Integer>> ineligibleSensors;
final boolean credentialAvailable;
final boolean confirmationRequested;
+ final boolean ignoreEnrollmentState;
+ final int userId;
+ final Context context;
static PreAuthInfo create(ITrustManager trustManager,
DevicePolicyManager devicePolicyManager,
BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
- boolean checkDevicePolicyManager)
+ boolean checkDevicePolicyManager, Context context)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -114,14 +121,23 @@ class PreAuthInfo {
@AuthenticatorStatus int status = getStatusForBiometricAuthenticator(
devicePolicyManager, settingObserver, sensor, userId, opPackageName,
checkDevicePolicyManager, requestedStrength,
- promptInfo.getAllowedSensorIds());
+ promptInfo.getAllowedSensorIds(),
+ promptInfo.isIgnoreEnrollmentState(),
+ context);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
+ " Modality: " + sensor.modality
+ " Status: " + status);
- if (status == AUTHENTICATOR_OK) {
+ // A sensor with privacy enabled will still be eligible to
+ // authenticate with biometric prompt. This is so the framework can display
+ // a sensor privacy error message to users after briefly showing the
+ // Biometric Prompt.
+ //
+ // Note: if only a certain sensor is required and the privacy is enabled,
+ // canAuthenticate() will return false.
+ if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
eligibleSensors.add(sensor);
} else {
ineligibleSensors.add(new Pair<>(sensor, status));
@@ -130,7 +146,8 @@ class PreAuthInfo {
}
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
- eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested);
+ eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
+ promptInfo.isIgnoreEnrollmentState(), userId, context);
}
/**
@@ -145,7 +162,8 @@ class PreAuthInfo {
BiometricService.SettingObserver settingObserver,
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
- @NonNull List<Integer> requestedSensorIds) {
+ @NonNull List<Integer> requestedSensorIds,
+ boolean ignoreEnrollmentState, Context context) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
@@ -167,9 +185,20 @@ class PreAuthInfo {
return BIOMETRIC_HARDWARE_NOT_DETECTED;
}
- if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)) {
+ if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)
+ && !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED;
}
+ final SensorPrivacyManager sensorPrivacyManager = context
+ .getSystemService(SensorPrivacyManager.class);
+
+ if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
+ if (sensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+ return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ }
+ }
+
final @LockoutTracker.LockoutMode int lockoutMode =
sensor.impl.getLockoutModeForUser(userId);
@@ -238,7 +267,8 @@ class PreAuthInfo {
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
- boolean confirmationRequested) {
+ boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
+ Context context) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
this.credentialRequested = credentialRequested;
@@ -247,6 +277,9 @@ class PreAuthInfo {
this.ineligibleSensors = ineligibleSensors;
this.credentialAvailable = credentialAvailable;
this.confirmationRequested = confirmationRequested;
+ this.ignoreEnrollmentState = ignoreEnrollmentState;
+ this.userId = userId;
+ this.context = context;
}
private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
@@ -274,15 +307,35 @@ class PreAuthInfo {
private Pair<Integer, Integer> getInternalStatus() {
@AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
+
+ final SensorPrivacyManager sensorPrivacyManager = context
+ .getSystemService(SensorPrivacyManager.class);
+
+ boolean cameraPrivacyEnabled = false;
+ if (sensorPrivacyManager != null) {
+ cameraPrivacyEnabled = sensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+ }
+
if (mBiometricRequested && credentialRequested) {
if (credentialAvailable || !eligibleSensors.isEmpty()) {
- status = AUTHENTICATOR_OK;
- if (credentialAvailable) {
- modality |= TYPE_CREDENTIAL;
- }
for (BiometricSensor sensor : eligibleSensors) {
modality |= sensor.modality;
}
+
+ if (credentialAvailable) {
+ modality |= TYPE_CREDENTIAL;
+ status = AUTHENTICATOR_OK;
+ } else if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face, credential is unavailable,
+ // and the face sensor privacy is enabled then return
+ // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will still be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
} else {
// Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) {
@@ -296,10 +349,18 @@ class PreAuthInfo {
}
} else if (mBiometricRequested) {
if (!eligibleSensors.isEmpty()) {
- status = AUTHENTICATOR_OK;
- for (BiometricSensor sensor : eligibleSensors) {
- modality |= sensor.modality;
- }
+ for (BiometricSensor sensor : eligibleSensors) {
+ modality |= sensor.modality;
+ }
+ if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face and the privacy is enabled
+ // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will still be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
} else {
// Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) {
@@ -320,9 +381,9 @@ class PreAuthInfo {
Slog.e(TAG, "No authenticators requested");
status = BIOMETRIC_NO_HARDWARE;
}
-
Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality
+ " AuthenticatorStatus: " + status);
+
return new Pair<>(modality, status);
}
@@ -356,6 +417,7 @@ class PreAuthInfo {
case CREDENTIAL_NOT_ENROLLED:
case BIOMETRIC_LOCKOUT_TIMED:
case BIOMETRIC_LOCKOUT_PERMANENT:
+ case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
break;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 996f0fd3a55f..0e2582c23b86 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -33,6 +33,7 @@ import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_LOCKOUT_TIMED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
+import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
import android.annotation.NonNull;
@@ -50,7 +51,6 @@ import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
import android.os.RemoteException;
@@ -62,7 +62,6 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.widget.LockPatternUtils;
-import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import java.util.List;
@@ -280,6 +279,9 @@ public class Utils {
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
break;
+ case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
default:
Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -339,7 +341,8 @@ public class Utils {
case BIOMETRIC_LOCKOUT_PERMANENT:
return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-
+ case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
+ return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
case BIOMETRIC_HARDWARE_NOT_DETECTED:
case BIOMETRIC_NOT_ENABLED_FOR_APPS:
@@ -541,14 +544,4 @@ public class Utils {
throw new IllegalArgumentException("Unknown strength: " + strength);
}
}
-
- public static int getUdfpsAuthReason(@NonNull AuthenticationClient<?> client) {
- if (client.isKeyguard()) {
- return IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD;
- } else if (client.isBiometricPrompt()) {
- return IUdfpsOverlayController.REASON_AUTH_BP;
- } else {
- return IUdfpsOverlayController.REASON_AUTH_FPM_OTHER;
- }
- }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 6f38ed04cd96..358263df916b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -28,6 +28,7 @@ import android.content.pm.ApplicationInfo;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.os.IBinder;
import android.os.RemoteException;
@@ -167,6 +168,10 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
return Utils.isKeyguard(getContext(), getOwnerString());
}
+ private boolean isSettings() {
+ return Utils.isSettings(getContext(), getOwnerString());
+ }
+
@Override
protected boolean isCryptoOperation() {
return mOperationId != 0;
@@ -246,7 +251,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
"Successful background authentication!");
}
- mAlreadyDone = true;
+ markAlreadyDone();
if (mTaskStackListener != null) {
mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
@@ -322,7 +327,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
final @LockoutTracker.LockoutMode int lockoutMode =
handleFailedAttempt(getTargetUserId());
if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
- mAlreadyDone = true;
+ markAlreadyDone();
}
final CoexCoordinator coordinator = CoexCoordinator.getInstance();
@@ -359,6 +364,43 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
}
}
+ /**
+ * Only call this method on interfaces where lockout does not come from onError, I.E. the
+ * old HIDL implementation.
+ */
+ protected void onLockoutTimed(long durationMillis) {
+ final ClientMonitorCallbackConverter listener = getListener();
+ final CoexCoordinator coordinator = CoexCoordinator.getInstance();
+ coordinator.onAuthenticationError(this, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
+ new CoexCoordinator.ErrorCallback() {
+ @Override
+ public void sendHapticFeedback() {
+ if (listener != null && mShouldVibrate) {
+ vibrateError();
+ }
+ }
+ });
+ }
+
+ /**
+ * Only call this method on interfaces where lockout does not come from onError, I.E. the
+ * old HIDL implementation.
+ */
+ protected void onLockoutPermanent() {
+ final ClientMonitorCallbackConverter listener = getListener();
+ final CoexCoordinator coordinator = CoexCoordinator.getInstance();
+ coordinator.onAuthenticationError(this,
+ BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
+ new CoexCoordinator.ErrorCallback() {
+ @Override
+ public void sendHapticFeedback() {
+ if (listener != null && mShouldVibrate) {
+ vibrateError();
+ }
+ }
+ });
+ }
+
private void sendCancelOnly(@Nullable ClientMonitorCallbackConverter listener) {
if (listener == null) {
Slog.e(TAG, "Unable to sendAuthenticationCanceled, listener null");
@@ -457,4 +499,20 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T>
public boolean wasAuthAttempted() {
return mAuthAttempted;
}
+
+ protected int getShowOverlayReason() {
+ if (isKeyguard()) {
+ return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
+ } else if (isBiometricPrompt()) {
+ // BP reason always takes precedent over settings, since callers from within
+ // settings can always invoke BP.
+ return BiometricOverlayConstants.REASON_AUTH_BP;
+ } else if (isSettings()) {
+ // This is pretty much only for FingerprintManager#authenticate usage from
+ // FingerprintSettings.
+ return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
+ } else {
+ return BiometricOverlayConstants.REASON_AUTH_OTHER;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index 9764a167fbbf..b73e91173a43 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -114,7 +114,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
private final int mCookie;
- boolean mAlreadyDone;
+ private boolean mAlreadyDone = false;
// Use an empty callback by default since delayed operations can receive events
// before they are started and cause NPE in subclasses that access this field directly.
@@ -202,11 +202,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor
return callback;
}
- public boolean isAlreadyDone() {
- return mAlreadyDone;
- }
-
- public void destroy() {
+ /** Signals this operation has completed its lifecycle and should no longer be used. */
+ void destroy() {
+ mAlreadyDone = true;
if (mToken != null) {
try {
mToken.unlinkToDeath(this, 0);
@@ -218,6 +216,20 @@ public abstract class BaseClientMonitor extends LoggableMonitor
}
}
+ /**
+ * Call while the operation is still active, but nearly done, to prevent any action
+ * upon client death (only needed for authentication clients).
+ */
+ void markAlreadyDone() {
+ Slog.d(TAG, "marking operation as done: " + this);
+ mAlreadyDone = true;
+ }
+
+ /** If this operation has been marked as completely done (or cancelled). */
+ public boolean isAlreadyDone() {
+ return mAlreadyDone;
+ }
+
@Override
public void binderDied() {
binderDiedInternal(true /* clearListener */);
@@ -225,10 +237,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor
// TODO(b/157790417): Move this to the scheduler
void binderDiedInternal(boolean clearListener) {
- Slog.e(TAG, "Binder died, owner: " + getOwnerString()
- + ", operation: " + this.getClass().getName());
+ Slog.e(TAG, "Binder died, operation: " + this);
- if (isAlreadyDone()) {
+ if (mAlreadyDone) {
Slog.w(TAG, "Binder died but client is finished, ignoring");
return;
}
@@ -299,7 +310,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
@Override
public String toString() {
return "{[" + mSequentialId + "] "
- + this.getClass().getSimpleName()
+ + this.getClass().getName()
+ ", proto=" + getProtoEnum()
+ ", owner=" + getOwnerString()
+ ", cookie=" + getCookie()
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 361ec40f2877..a358bc2bad55 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -605,6 +605,9 @@ public class BiometricScheduler {
if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) {
Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation);
// We can set it to null immediately, since the HAL was never notified to start.
+ if (mCurrentOperation != null) {
+ mCurrentOperation.mClientMonitor.destroy();
+ }
mCurrentOperation = null;
startNextOperationIfIdle();
return;
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 9191b8b55989..2826e0c97305 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -19,7 +19,9 @@ package com.android.server.biometrics.sensors;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -128,4 +130,15 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
public boolean interruptsPrecedingClients() {
return true;
}
+
+ protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
+ switch (reason) {
+ case FingerprintManager.ENROLL_FIND_SENSOR:
+ return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
+ case FingerprintManager.ENROLL_ENROLL:
+ return BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
+ default:
+ return BiometricOverlayConstants.REASON_UNKNOWN;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
new file mode 100644
index 000000000000..008717899aba
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -0,0 +1,144 @@
+/*
+ * 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.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Single entry point & holder for controllers managing UI overlays for biometrics.
+ *
+ * For common operations, like {@link #show(int, int, AcquisitionClient)}, modalities are
+ * skipped if they are not present (provided as null via the constructor).
+ *
+ * Use the getters, such as {@link #ifUdfps(OverlayControllerConsumer)}, to get a controller for
+ * operations that are unique to a single modality.
+ */
+public final class SensorOverlays {
+
+ private static final String TAG = "SensorOverlays";
+
+ @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
+ @NonNull private final Optional<ISidefpsController> mSidefpsController;
+
+ /**
+ * Create an overlay controller for each modality.
+ *
+ * @param udfpsOverlayController under display fps or null if not present on device
+ * @param sidefpsController side fps or null if not present on device
+ */
+ public SensorOverlays(
+ @Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable ISidefpsController sidefpsController) {
+ mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
+ mSidefpsController = Optional.ofNullable(sidefpsController);
+ }
+
+ /**
+ * Show the overlay.
+ *
+ * @param sensorId sensor id
+ * @param reason reason for showing
+ * @param client client performing operation
+ */
+ public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason,
+ @NonNull AcquisitionClient<?> client) {
+ if (mSidefpsController.isPresent()) {
+ try {
+ mSidefpsController.get().show(sensorId, reason);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when showing the side-fps overlay", e);
+ }
+ }
+
+ if (mUdfpsOverlayController.isPresent()) {
+ final IUdfpsOverlayControllerCallback callback =
+ new IUdfpsOverlayControllerCallback.Stub() {
+ @Override
+ public void onUserCanceled() {
+ client.onUserCanceled();
+ }
+ };
+
+ try {
+ mUdfpsOverlayController.get().showUdfpsOverlay(sensorId, reason, callback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
+ }
+ }
+ }
+
+ /**
+ * Hide the overlay.
+ *
+ * @param sensorId sensor id
+ */
+ public void hide(int sensorId) {
+ if (mSidefpsController.isPresent()) {
+ try {
+ mSidefpsController.get().hide(sensorId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e);
+ }
+ }
+
+ if (mUdfpsOverlayController.isPresent()) {
+ try {
+ mUdfpsOverlayController.get().hideUdfpsOverlay(sensorId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
+ }
+ }
+ }
+
+ /**
+ * Use the udfps controller, if present.
+ * @param consumer action
+ */
+ public void ifUdfps(OverlayControllerConsumer<IUdfpsOverlayController> consumer) {
+ if (mUdfpsOverlayController.isPresent()) {
+ try {
+ consumer.accept(mUdfpsOverlayController.get());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception using overlay controller", e);
+ }
+ }
+ }
+
+ /**
+ * Consumer for a biometric overlay controller.
+ *
+ * This behaves like a normal {@link Consumer} except that it will trap and log
+ * any thrown {@link RemoteException}.
+ *
+ * @param <T> the type of the input to the operation
+ **/
+ @FunctionalInterface
+ public interface OverlayControllerConsumer<T> {
+ /** Perform the operation. */
+ void accept(T t) throws RemoteException;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index cbceba6cc959..4131ae127ab2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -56,6 +57,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@NonNull private final LockoutCache mLockoutCache;
@Nullable private final NotificationManager mNotificationManager;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private SensorPrivacyManager mSensorPrivacyManager;
private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor;
@@ -81,6 +83,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -108,7 +111,16 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@Override
protected void startHalOperation() {
try {
- mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ getTargetUserId())) {
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ } else {
+ mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
@@ -225,6 +237,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@Override
public void onLockoutTimed(long durationMillis) {
+ super.onLockoutTimed(durationMillis);
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT;
@@ -239,6 +252,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@Override
public void onLockoutPermanent() {
+ super.onLockoutPermanent();
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 2ef0911658b1..2158dfe7bde5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -19,6 +19,8 @@ package com.android.server.biometrics.sensors.face.aidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.ISession;
@@ -41,6 +43,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
private final boolean mIsStrongBiometric;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private SensorPrivacyManager mSensorPrivacyManager;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@NonNull IBinder token, long requestId,
@@ -51,6 +54,7 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
}
@Override
@@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
@Override
protected void startHalOperation() {
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
try {
mCancellationSignal = getFreshDaemon().detectInteraction();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
index c364dbb4d615..2b5f49546d69 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.ISessionCallback;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -52,6 +53,7 @@ public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
try {
final ISession newSession = getFreshDaemon().createSession(getSensorId(),
getTargetUserId(), mSessionCallback);
+ Binder.allowBlocking(newSession.asBinder());
mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
getCallback().onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 40f2801541d3..7548d2871a15 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face.hidl;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -55,6 +56,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
private final int[] mKeyguardIgnoreListVendor;
private int mLastAcquire;
+ private SensorPrivacyManager mSensorPrivacyManager;
FaceAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
@Override
protected void startHalOperation() {
+
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
try {
getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index f35bb7ffd26b..c5d33ed7400b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -281,7 +281,7 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public long authenticate(final IBinder token, final long operationId,
final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
- final String opPackageName) {
+ final String opPackageName, boolean ignoreEnrollmentState) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -333,7 +333,8 @@ public class FingerprintService extends SystemService {
&& sensorProps != null && sensorProps.isAnyUdfpsType()) {
identity = Binder.clearCallingIdentity();
try {
- return authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+ return authenticateWithPrompt(operationId, sensorProps, userId, receiver,
+ ignoreEnrollmentState);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -347,7 +348,8 @@ public class FingerprintService extends SystemService {
final long operationId,
@NonNull final FingerprintSensorPropertiesInternal props,
final int userId,
- final IFingerprintServiceReceiver receiver) {
+ final IFingerprintServiceReceiver receiver,
+ boolean ignoreEnrollmentState) {
final Context context = getUiContext();
final Executor executor = context.getMainExecutor();
@@ -368,6 +370,7 @@ public class FingerprintService extends SystemService {
})
.setAllowedSensorIds(new ArrayList<>(
Collections.singletonList(props.sensorId)))
+ .setIgnoreEnrollmentState(ignoreEnrollmentState)
.build();
final BiometricPrompt.AuthenticationCallback promptCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 0050a895034f..be0e6edb2a42 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,7 +23,6 @@ import static android.hardware.fingerprint.FingerprintStateListener.STATE_IDLE;
import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
-import android.content.Context;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintStateListener;
import android.os.RemoteException;
@@ -34,8 +33,6 @@ import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.EnrollmentModifier;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -70,7 +67,7 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback {
} else {
mFingerprintState = STATE_AUTH_OTHER;
}
- } else if (client instanceof FingerprintEnrollClient) {
+ } else if (client instanceof EnrollClient) {
mFingerprintState = STATE_ENROLLING;
} else {
Slog.w(FingerprintService.TAG,
@@ -143,6 +140,7 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback {
/**
* Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
* updates in fingerprint sensor state to the SideFpNsEventHandler
+ *
* @param listener
*/
public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java
deleted file mode 100644
index 474066c227d2..000000000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/SidefpsHelper.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint;
-
-import android.annotation.Nullable;
-import android.hardware.fingerprint.ISidefpsController;
-import android.os.RemoteException;
-import android.util.Slog;
-
-/**
- * Contains helper methods for side-fps fingerprint controller.
- */
-public class SidefpsHelper {
- private static final String TAG = "SidefpsHelper";
-
- /**
- * Shows the side-fps affordance
- * @param sidefpsController controller that shows and hides the side-fps affordance
- */
- public static void showOverlay(@Nullable ISidefpsController sidefpsController) {
- if (sidefpsController == null) {
- return;
- }
-
- try {
- sidefpsController.show();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when showing the side-fps overlay", e);
- }
- }
-
- /**
- * Hides the side-fps affordance
- * @param sidefpsController controller that shows and hides the side-fps affordance
- */
- public static void hideOverlay(@Nullable ISidefpsController sidefpsController) {
- if (sidefpsController == null) {
- return;
- }
- try {
- sidefpsController.hide();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
index 879c8a0317d7..29661d46f328 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/UdfpsHelper.java
@@ -17,17 +17,12 @@
package com.android.server.biometrics.sensors.fingerprint;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-
/**
* Contains helper methods for under-display fingerprint HIDL.
*/
@@ -68,88 +63,6 @@ public class UdfpsHelper {
}
}
- public static int getReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
- switch (reason) {
- case FingerprintManager.ENROLL_FIND_SENSOR:
- return IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR;
- case FingerprintManager.ENROLL_ENROLL:
- return IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
- default:
- return IUdfpsOverlayController.REASON_UNKNOWN;
- }
- }
-
- public static void showUdfpsOverlay(int sensorId, int reason,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- @NonNull AcquisitionClient<?> client) {
- if (udfpsOverlayController == null) {
- return;
- }
-
- final IUdfpsOverlayControllerCallback callback =
- new IUdfpsOverlayControllerCallback.Stub() {
- @Override
- public void onUserCanceled() {
- client.onUserCanceled();
- }
- };
-
- try {
- udfpsOverlayController.showUdfpsOverlay(sensorId, reason, callback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
- }
- }
-
- public static void hideUdfpsOverlay(int sensorId,
- @Nullable IUdfpsOverlayController udfpsOverlayController) {
- if (udfpsOverlayController == null) {
- return;
- }
- try {
- udfpsOverlayController.hideUdfpsOverlay(sensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
- }
- }
-
- public static void onAcquiredGood(int sensorId,
- @Nullable IUdfpsOverlayController udfpsOverlayController) {
- if (udfpsOverlayController == null) {
- return;
- }
-
- try {
- udfpsOverlayController.onAcquiredGood(sensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onAcquiredGood", e);
- }
- }
-
- public static void onEnrollmentProgress(int sensorId, int remaining,
- @Nullable IUdfpsOverlayController udfpsOverlayController) {
- if (udfpsOverlayController == null) {
- return;
- }
- try {
- udfpsOverlayController.onEnrollmentProgress(sensorId, remaining);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onEnrollmentProgress", e);
- }
- }
-
- public static void onEnrollmentHelp(int sensorId,
- @Nullable IUdfpsOverlayController udfpsOverlayController) {
- if (udfpsOverlayController == null) {
- return;
- }
- try {
- udfpsOverlayController.onEnrollmentHelp(sensorId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onEnrollmentHelp", e);
- }
- }
-
public static boolean isValidAcquisitionMessage(@NonNull Context context,
int acquireInfo, int vendorCode) {
return FingerprintManager.getAcquiredString(context, acquireInfo, vendorCode) != null;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index b405be75bdf1..ca051e9e9bf4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -27,20 +27,20 @@ import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
import java.util.ArrayList;
@@ -53,7 +53,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
private static final String TAG = "FingerprintAuthenticationClient";
@NonNull private final LockoutCache mLockoutCache;
- @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+ @NonNull private final SensorOverlays mSensorOverlays;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
@@ -68,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
int sensorId, boolean isStrongBiometric, int statsClient,
@Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
@@ -77,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
- mUdfpsOverlayController = udfpsOverlayController;
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = createALSCallback(false /* startWithClient */);
}
@@ -120,7 +121,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
if (authenticated) {
mState = STATE_STOPPED;
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
}
@@ -131,7 +132,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
// For UDFPS, notify SysUI that the illumination can be turned off.
// See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE
if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD) {
- UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId()));
}
super.onAcquired(acquiredInfo, vendorCode);
@@ -145,27 +146,27 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
BiometricNotificationUtils.showBadCalibrationNotification(getContext());
}
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
}
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
- mUdfpsOverlayController, this);
+ mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
+
try {
mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
@@ -239,7 +240,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
Slog.e(TAG, "Remote exception", e);
}
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
@@ -256,7 +257,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
Slog.e(TAG, "Remote exception", e);
}
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index da91cdd981b9..ac3ce896049b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.fingerprint.ISession;
@@ -31,7 +32,7 @@ import com.android.server.biometrics.BiometricsProto;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.DetectionConsumer;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
+import com.android.server.biometrics.sensors.SensorOverlays;
/**
* Performs fingerprint detection without exposing any matching information (e.g. accept/reject
@@ -42,8 +43,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det
private static final String TAG = "FingerprintDetectClient";
private final boolean mIsStrongBiometric;
- @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
-
+ @NonNull private final SensorOverlays mSensorOverlays;
@Nullable private ICancellationSignal mCancellationSignal;
FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@@ -57,7 +57,7 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
- mUdfpsOverlayController = udfpsOverlayController;
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
}
@Override
@@ -68,7 +68,8 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
+
try {
mCancellationSignal.cancel();
} catch (RemoteException e) {
@@ -79,14 +80,13 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(getSensorId(),
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
- mUdfpsOverlayController, this);
+ mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+
try {
mCancellationSignal = getFreshDaemon().detectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting finger detect", e);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index c420c5c57241..ccb34aad3198 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -39,8 +39,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -49,8 +49,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
private static final String TAG = "FingerprintEnrollClient";
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
- @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
- @Nullable private final ISidefpsController mSidefpsController;
+ @NonNull private final SensorOverlays mSensorOverlays;
private final @FingerprintManager.EnrollReason int mEnrollReason;
@Nullable private ICancellationSignal mCancellationSignal;
@@ -63,7 +62,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
@NonNull byte[] hardwareAuthToken, @NonNull String owner,
@NonNull BiometricUtils<Fingerprint> utils, int sensorId,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
- @Nullable IUdfpsOverlayController udfpsOvelayController,
+ @Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
@@ -71,8 +70,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
!sensorProps.isAnyUdfpsType() /* shouldVibrate */);
mSensorProps = sensorProps;
- mUdfpsOverlayController = udfpsOvelayController;
- mSidefpsController = sidefpsController;
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mMaxTemplatesPerUser = maxTemplatesPerUser;
mEnrollReason = enrollReason;
@@ -91,11 +89,11 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
super.onEnrollResult(identifier, remaining);
- UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+ mSensorOverlays.ifUdfps(
+ controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
if (remaining == 0) {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
}
}
@@ -106,12 +104,14 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
if (acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD
&& mSensorProps.isAnyUdfpsType()) {
vibrateSuccess();
- UdfpsHelper.onAcquiredGood(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.ifUdfps(controller -> controller.onAcquiredGood(getSensorId()));
}
- if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
- UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
- }
+ mSensorOverlays.ifUdfps(controller -> {
+ if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+ controller.onEnrollmentHelp(getSensorId());
+ }
+ });
super.onAcquired(acquiredInfo, vendorCode);
}
@@ -120,8 +120,7 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
public void onError(int errorCode, int vendorCode) {
super.onError(errorCode, vendorCode);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
}
@Override
@@ -133,8 +132,8 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
+
if (mCancellationSignal != null) {
try {
mCancellationSignal.cancel();
@@ -149,10 +148,8 @@ class FingerprintEnrollClient extends EnrollClient<ISession> implements Udfps {
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(getSensorId(),
- UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
- mUdfpsOverlayController, this);
- SidefpsHelper.showOverlay(mSidefpsController);
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
mCancellationSignal = getFreshDaemon().enroll(
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 ca83dda3bc4e..0defc3fb6a50 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
@@ -25,10 +25,12 @@ import android.app.ActivityTaskManager;
import android.app.TaskStackListener;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.res.TypedArray;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
@@ -145,6 +147,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
+ final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
+
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -164,9 +168,12 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
componentInfo,
prop.sensorType,
true /* resetLockoutRequiresHardwareAuthToken */,
- prop.sensorLocations[0].sensorLocationX,
- prop.sensorLocations[0].sensorLocationY,
- prop.sensorLocations[0].sensorRadius);
+ !workaroundLocations.isEmpty() ? workaroundLocations :
+ List.of(new SensorLocationInternal(
+ "" /* displayId */,
+ prop.sensorLocations[0].sensorLocationX,
+ prop.sensorLocations[0].sensorLocationY,
+ prop.sensorLocations[0].sensorRadius)));
final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler,
internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher);
@@ -403,7 +410,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
- mUdfpsOverlayController, allowBackgroundAuthentication,
+ mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
@@ -647,4 +654,45 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
void setTestHalEnabled(boolean enabled) {
mTestHalEnabled = enabled;
}
+
+ // TODO(b/174868353): workaround for gaps in HAL interface (remove and get directly from HAL)
+ // reads values via an overlay instead of querying the HAL
+ @NonNull
+ private List<SensorLocationInternal> getWorkaroundSensorProps(@NonNull Context context) {
+ final List<SensorLocationInternal> sensorLocations = new ArrayList<>();
+
+ final TypedArray sfpsProps = context.getResources().obtainTypedArray(
+ com.android.internal.R.array.config_sfps_sensor_props);
+ for (int i = 0; i < sfpsProps.length(); i++) {
+ final int id = sfpsProps.getResourceId(i, -1);
+ if (id > 0) {
+ final SensorLocationInternal location = parseSensorLocation(
+ context.getResources().obtainTypedArray(id));
+ if (location != null) {
+ sensorLocations.add(location);
+ }
+ }
+ }
+ sfpsProps.recycle();
+
+ return sensorLocations;
+ }
+
+ @Nullable
+ private SensorLocationInternal parseSensorLocation(@Nullable TypedArray array) {
+ if (array == null) {
+ return null;
+ }
+
+ try {
+ return new SensorLocationInternal(
+ array.getString(0),
+ array.getInt(1, 0),
+ array.getInt(2, 0),
+ array.getInt(3, 0));
+ } catch (Exception e) {
+ Slog.w(getTag(), "malformed sensor location", e);
+ }
+ return null;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
index 2d40c91cbc75..ee81620fdf77 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
@@ -53,6 +54,7 @@ public class FingerprintStartUserClient extends StartUserClient<IFingerprint, IS
try {
final ISession newSession = getFreshDaemon().createSession(getSensorId(),
getTargetUserId(), mSessionCallback);
+ Binder.allowBlocking(newSession.asBinder());
mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
getCallback().onClientFinished(this, true /* success */);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index d2882aa4094c..5f2f4cf6ef3c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -629,7 +629,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
restricted, opPackageName, cookie, false /* requireConfirmation */,
mSensorProperties.sensorId, isStrongBiometric, statsClient,
- mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
+ mTaskStackListener, mLockoutTracker,
+ mUdfpsOverlayController, mSidefpsController,
allowBackgroundAuthentication, mSensorProperties);
mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 79ad8e1a5c70..dd68b4d37e2a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -426,8 +426,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- resetLockoutRequiresHardwareAuthToken, sensorProps.sensorLocationX,
- sensorProps.sensorLocationY, sensorProps.sensorRadius);
+ resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
mMockHalResultController = controller;
mUserHasTrust = new SparseBooleanArray();
mTrustManager = context.getSystemService(TrustManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 7d95ec098fee..3058e2508f5f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,16 +26,17 @@ import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutTracker;
+import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -52,7 +53,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
private static final String TAG = "Biometrics/FingerprintAuthClient";
private final LockoutFrameworkImpl mLockoutFrameworkImpl;
- @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+ @NonNull private final SensorOverlays mSensorOverlays;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
@@ -67,6 +68,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@NonNull TaskStackListener taskStackListener,
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
@@ -76,7 +78,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
- mUdfpsOverlayController = udfpsOverlayController;
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = createALSCallback(false /* startWithClient */);
}
@@ -112,7 +114,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
if (authenticated) {
mState = STATE_STOPPED;
resetFailedAttempts(getTargetUserId());
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
final @LockoutTracker.LockoutMode int lockoutMode =
@@ -125,7 +127,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
// Send the error, but do not invoke the FinishCallback yet. Since lockout is not
// controlled by the HAL, the framework must stop the sensor before finishing the
// client.
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
cancel();
}
@@ -140,7 +142,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
BiometricNotificationUtils.showBadCalibrationNotification(getContext());
}
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
}
private void resetFailedAttempts(int userId) {
@@ -168,8 +170,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(getSensorId(), Utils.getUdfpsAuthReason(this),
- mUdfpsOverlayController, this);
+ mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
+
try {
// GroupId was never used. In fact, groupId is always the same as userId.
getFreshDaemon().authenticate(mOperationId, getTargetUserId());
@@ -177,14 +179,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
+
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 147a20699b54..b854fb300ece 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.PerformanceTracker;
+import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -48,7 +50,7 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
private static final String TAG = "FingerprintDetectClient";
private final boolean mIsStrongBiometric;
- @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
+ @NonNull private final SensorOverlays mSensorOverlays;
private boolean mIsPointerDown;
public FingerprintDetectClient(@NonNull Context context,
@@ -61,13 +63,14 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
setRequestId(requestId);
- mUdfpsOverlayController = udfpsOverlayController;
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
mIsStrongBiometric = isStrongBiometric;
}
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
+
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -86,16 +89,15 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(getSensorId(),
- IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD,
- mUdfpsOverlayController, this);
+ mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+
try {
getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index dc705346f534..1ebf44ca707f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -35,7 +35,7 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.fingerprint.SidefpsHelper;
+import com.android.server.biometrics.sensors.SensorOverlays;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
@@ -49,8 +49,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
private static final String TAG = "FingerprintEnrollClient";
- @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
- @Nullable private final ISidefpsController mSidefpsController;
+ @NonNull private final SensorOverlays mSensorOverlays;
private final @FingerprintManager.EnrollReason int mEnrollReason;
private boolean mIsPointerDown;
@@ -65,8 +64,7 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId,
true /* shouldVibrate */);
- mUdfpsOverlayController = udfpsOverlayController;
- mSidefpsController = sidefpsController;
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -95,10 +93,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
@Override
protected void startHalOperation() {
- UdfpsHelper.showUdfpsOverlay(getSensorId(),
- UdfpsHelper.getReasonFromEnrollReason(mEnrollReason),
- mUdfpsOverlayController, this);
- SidefpsHelper.showOverlay(mSidefpsController);
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
// GroupId was never used. In fact, groupId is always the same as userId.
@@ -107,16 +103,15 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
}
@Override
protected void stopHalOperation() {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
+
try {
getFreshDaemon().cancel();
} catch (RemoteException e) {
@@ -131,11 +126,11 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
super.onEnrollResult(identifier, remaining);
- UdfpsHelper.onEnrollmentProgress(getSensorId(), remaining, mUdfpsOverlayController);
+ mSensorOverlays.ifUdfps(
+ controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
if (remaining == 0) {
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
}
}
@@ -143,17 +138,18 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
public void onAcquired(int acquiredInfo, int vendorCode) {
super.onAcquired(acquiredInfo, vendorCode);
- if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
- UdfpsHelper.onEnrollmentHelp(getSensorId(), mUdfpsOverlayController);
- }
+ mSensorOverlays.ifUdfps(controller -> {
+ if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
+ controller.onEnrollmentHelp(getSensorId());
+ }
+ });
}
@Override
public void onError(int errorCode, int vendorCode) {
super.onError(errorCode, vendorCode);
- UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- SidefpsHelper.hideOverlay(mSidefpsController);
+ mSensorOverlays.hide(getSensorId());
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 54a4ad42fd99..23f0ffbbf0b8 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -52,11 +52,11 @@ public class BroadcastRadioService extends SystemService {
public BroadcastRadioService(Context context) {
super(context);
- mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService();
+ mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(mLock);
mV1Modules = mHal1.loadModules();
OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max();
mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService(
- max.isPresent() ? max.getAsInt() + 1 : 0);
+ max.isPresent() ? max.getAsInt() + 1 : 0, mLock);
}
@Override
@@ -111,7 +111,7 @@ public class BroadcastRadioService extends SystemService {
synchronized (mLock) {
if (!mHal2.hasAnyModules()) {
Slog.i(TAG, "There are no HAL 2.x modules registered");
- return new AnnouncementAggregator(listener);
+ return new AnnouncementAggregator(listener, mLock);
}
return mHal2.addAnnouncementListener(enabledTypes, listener);
diff --git a/services/core/java/com/android/server/broadcastradio/OWNERS b/services/core/java/com/android/server/broadcastradio/OWNERS
index ea4421eae96a..3e360e7e992c 100644
--- a/services/core/java/com/android/server/broadcastradio/OWNERS
+++ b/services/core/java/com/android/server/broadcastradio/OWNERS
@@ -1,2 +1,3 @@
+keunyoung@google.com
+oscarazu@google.com
twasilczyk@google.com
-randolphs@google.com
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
index e8ac5477469b..5da60328cd70 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/BroadcastRadioService.java
@@ -17,16 +17,9 @@
package com.android.server.broadcastradio.hal1;
import android.annotation.NonNull;
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.radio.IRadioService;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
-import android.os.ParcelableException;
-
-import com.android.server.SystemService;
import java.util.List;
import java.util.Objects;
@@ -37,7 +30,7 @@ public class BroadcastRadioService {
*/
private final long mNativeContext = nativeInit();
- private final Object mLock = new Object();
+ private final Object mLock;
@Override
protected void finalize() throws Throwable {
@@ -51,6 +44,14 @@ public class BroadcastRadioService {
private native Tuner nativeOpenTuner(long nativeContext, int moduleId,
RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback);
+ /**
+ * Constructor. should pass
+ * {@code com.android.server.broadcastradio.BroadcastRadioService#mLock} for lock.
+ */
+ public BroadcastRadioService(@NonNull Object lock) {
+ mLock = lock;
+ }
+
public @NonNull List<RadioManager.ModuleProperties> loadModules() {
synchronized (mLock) {
return Objects.requireNonNull(nativeLoadModules(mNativeContext));
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
index 53076975849b..42e296f3e4ec 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -35,7 +35,7 @@ import java.util.Objects;
public class AnnouncementAggregator extends ICloseHandle.Stub {
private static final String TAG = "BcRadio2Srv.AnnAggr";
- private final Object mLock = new Object();
+ private final Object mLock;
@NonNull private final IAnnouncementListener mListener;
private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient();
@@ -45,8 +45,9 @@ public class AnnouncementAggregator extends ICloseHandle.Stub {
@GuardedBy("mLock")
private boolean mIsClosed = false;
- public AnnouncementAggregator(@NonNull IAnnouncementListener listener) {
+ public AnnouncementAggregator(@NonNull IAnnouncementListener listener, @NonNull Object lock) {
mListener = Objects.requireNonNull(listener);
+ mLock = Objects.requireNonNull(lock);
try {
listener.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 5e79c5943d7b..5c07f76e5011 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -42,7 +42,7 @@ import java.util.stream.Collectors;
public class BroadcastRadioService {
private static final String TAG = "BcRadio2Srv";
- private final Object mLock = new Object();
+ private final Object mLock;
@GuardedBy("mLock")
private int mNextModuleId = 0;
@@ -68,7 +68,7 @@ public class BroadcastRadioService {
moduleId = mNextModuleId;
}
- RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName);
+ RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName, mLock);
if (module == null) {
return;
}
@@ -116,8 +116,9 @@ public class BroadcastRadioService {
}
};
- public BroadcastRadioService(int nextModuleId) {
+ public BroadcastRadioService(int nextModuleId, Object lock) {
mNextModuleId = nextModuleId;
+ mLock = lock;
try {
IServiceManager manager = IServiceManager.getService();
if (manager == null) {
@@ -174,7 +175,7 @@ public class BroadcastRadioService {
public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@NonNull IAnnouncementListener listener) {
- AnnouncementAggregator aggregator = new AnnouncementAggregator(listener);
+ AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock);
boolean anySupported = false;
synchronized (mLock) {
for (RadioModule module : mModules.values()) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index b7e188c73eab..ef7f4c9fc919 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -58,7 +58,7 @@ class RadioModule {
@NonNull private final IBroadcastRadio mService;
@NonNull public final RadioManager.ModuleProperties mProperties;
- private final Object mLock = new Object();
+ private final Object mLock;
@NonNull private final Handler mHandler;
@GuardedBy("mLock")
@@ -132,13 +132,15 @@ class RadioModule {
@VisibleForTesting
RadioModule(@NonNull IBroadcastRadio service,
- @NonNull RadioManager.ModuleProperties properties) {
+ @NonNull RadioManager.ModuleProperties properties, @NonNull Object lock) {
mProperties = Objects.requireNonNull(properties);
mService = Objects.requireNonNull(service);
+ mLock = Objects.requireNonNull(lock);
mHandler = new Handler(Looper.getMainLooper());
}
- public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName) {
+ public static @Nullable RadioModule tryLoadingModule(int idx, @NonNull String fqName,
+ Object lock) {
try {
IBroadcastRadio service = IBroadcastRadio.getService(fqName);
if (service == null) return null;
@@ -156,7 +158,7 @@ class RadioModule {
RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName,
service.getProperties(), amfmConfig.value, dabConfig.value);
- return new RadioModule(service, prop);
+ return new RadioModule(service, prop, lock);
} catch (RemoteException ex) {
Slog.e(TAG, "failed to load module " + fqName, ex);
return null;
@@ -178,7 +180,8 @@ class RadioModule {
});
mHalTunerSession = Objects.requireNonNull(hwSession.value);
}
- TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
+ TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb,
+ mLock);
mAidlTunerSessions.add(tunerSession);
// Propagate state to new client. Note: These callbacks are invoked while holding mLock
@@ -377,7 +380,7 @@ class RadioModule {
}
};
- synchronized (mService) {
+ synchronized (mLock) {
mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
halResult.value = result;
hwCloseHandle.value = closeHnd;
@@ -401,7 +404,7 @@ class RadioModule {
if (id == 0) throw new IllegalArgumentException("Image ID is missing");
byte[] rawImage;
- synchronized (mService) {
+ synchronized (mLock) {
List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
rawImage = new byte[rawList.size()];
for (int i = 0; i < rawList.size(); i++) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 7ab3bdd859e4..200af2fb1da7 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -40,7 +40,7 @@ class TunerSession extends ITuner.Stub {
private static final String TAG = "BcRadio2Srv.session";
private static final String kAudioDeviceName = "Radio tuner source";
- private final Object mLock = new Object();
+ private final Object mLock;
private final RadioModule mModule;
private final ITunerSession mHwSession;
@@ -53,10 +53,12 @@ class TunerSession extends ITuner.Stub {
private RadioManager.BandConfig mDummyConfig = null;
TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
- @NonNull android.hardware.radio.ITunerCallback callback) {
+ @NonNull android.hardware.radio.ITunerCallback callback,
+ @NonNull Object lock) {
mModule = Objects.requireNonNull(module);
mHwSession = Objects.requireNonNull(hwSession);
mCallback = Objects.requireNonNull(callback);
+ mLock = Objects.requireNonNull(lock);
}
@Override
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index c3ba80094305..3120dc58eebd 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,37 +15,49 @@
*/
package com.android.server.camera;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.os.Build.VERSION_CODES.M;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
-import android.app.TaskStackListener;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.hardware.CameraSessionStats;
import android.hardware.CameraStreamStats;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceProxy;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.nfc.INfcAdapter;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.os.UserManager;
import android.stats.camera.nano.CameraProtos.CameraStreamProto;
import android.util.ArrayMap;
@@ -57,8 +69,8 @@ import android.view.IDisplayWindowListener;
import android.view.Surface;
import android.view.WindowManagerGlobal;
-import com.android.internal.annotations.GuardedBy;
import com.android.framework.protobuf.nano.MessageNano;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -72,7 +84,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -94,6 +105,57 @@ public class CameraServiceProxy extends SystemService
public static final String CAMERA_SERVICE_PROXY_BINDER_NAME = "media.camera.proxy";
+ /**
+ * When enabled this change id forces the packages it is applied to override the default
+ * camera rotate & crop behavior and always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE .
+ * The default behavior along with all possible override combinations is discussed in the table
+ * below.
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS = 189229956L; // buganizer id
+
+ /**
+ * When enabled this change id forces the packages it is applied to ignore the current value of
+ * 'android:resizeableActivity' as well as target SDK equal to or below M and consider the
+ * activity as non-resizeable. In this case, the value of camera rotate & crop will only depend
+ * on the needed compensation considering the current display rotation.
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK = 191513214L; // buganizer id
+
+ /**
+ * Possible override combinations
+ *
+ * |OVERRIDE |OVERRIDE_
+ * |CAMERA_ |CAMERA_
+ * |ROTATE_ |RESIZEABLE_
+ * |AND_CROP_ |AND_SDK_
+ * |DEFAULTS |CHECK
+ * _________________________________________________
+ * Default Behavior | D |D
+ * _________________________________________________
+ * Ignore SDK&Resize | D |E
+ * _________________________________________________
+ * SCALER_ROTATE_AND_CROP_NONE | E |D, E
+ * _________________________________________________
+ * Where:
+ * E -> Override enabled
+ * D -> Override disabled
+ * Default behavior -> Rotate&crop will be calculated depending on the required
+ * compensation necessary for the current display rotation.
+ * Additionally the app must either target M (or below)
+ * or is declared as non-resizeable.
+ * Ignore SDK&Resize -> The Rotate&crop value will depend on the required
+ * compensation for the current display rotation.
+ * SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
+ */
+
// Flags arguments to NFC adapter to enable/disable NFC
public static final int DISABLE_POLLING_FLAGS = 0x1000;
public static final int ENABLE_POLLING_FLAGS = 0x0000;
@@ -246,62 +308,15 @@ public class CameraServiceProxy extends SystemService
private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener();
- private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
-
- private final class TaskInfo {
- private int frontTaskId;
- private boolean isResizeable;
- private boolean isFixedOrientationLandscape;
- private boolean isFixedOrientationPortrait;
- private int displayId;
+ public static final class TaskInfo {
+ public int frontTaskId;
+ public boolean isResizeable;
+ public boolean isFixedOrientationLandscape;
+ public boolean isFixedOrientationPortrait;
+ public int displayId;
+ public int userId;
}
- private final class TaskStateHandler extends TaskStackListener {
- private final Object mMapLock = new Object();
-
- // maps the package name to its corresponding current top level task id
- @GuardedBy("mMapLock")
- private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>();
-
- @Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- synchronized (mMapLock) {
- TaskInfo info = new TaskInfo();
- info.frontTaskId = taskInfo.taskId;
- info.isResizeable = taskInfo.isResizeable;
- info.displayId = taskInfo.displayId;
- info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape(
- taskInfo.topActivityInfo.screenOrientation);
- info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait(
- taskInfo.topActivityInfo.screenOrientation);
- mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info);
- }
- }
-
- @Override
- public void onTaskRemoved(int taskId) {
- synchronized (mMapLock) {
- for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){
- if (entry.getValue().frontTaskId == taskId) {
- mTaskInfoMap.remove(entry.getKey());
- break;
- }
- }
- }
- }
-
- public @Nullable TaskInfo getFrontTaskInfo(String packageName) {
- synchronized (mMapLock) {
- if (mTaskInfoMap.containsKey(packageName)) {
- return mTaskInfoMap.get(packageName);
- }
- }
-
- Log.e(TAG, "Top task with package name: " + packageName + " not found!");
- return null;
- }
- };
-
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -327,93 +342,181 @@ public class CameraServiceProxy extends SystemService
}
};
- private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
- private boolean isMOrBelow(Context ctx, String packageName) {
- try {
- return ctx.getPackageManager().getPackageInfo(
- packageName, 0).applicationInfo.targetSdkVersion <= M;
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG,"Package name not found!");
- }
- return false;
+ private static boolean isMOrBelow(Context ctx, String packageName) {
+ try {
+ return ctx.getPackageManager().getPackageInfo(
+ packageName, 0).applicationInfo.targetSdkVersion <= M;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG,"Package name not found!");
}
+ return false;
+ }
- /**
- * Gets whether crop-rotate-scale is needed.
- */
- private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
- @Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) {
- if (taskInfo == null) {
- return false;
- }
+ /**
+ * Estimate the app crop-rotate-scale compensation value.
+ */
+ public static int getCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
+ @Nullable TaskInfo taskInfo, int displayRotation, int lensFacing,
+ boolean ignoreResizableAndSdkCheck) {
+ if (taskInfo == null) {
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
- // External cameras do not need crop-rotate-scale.
- if (lensFacing != CameraMetadata.LENS_FACING_FRONT
- && lensFacing != CameraMetadata.LENS_FACING_BACK) {
- Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
- return false;
- }
+ // External cameras do not need crop-rotate-scale.
+ if (lensFacing != CameraMetadata.LENS_FACING_FRONT
+ && lensFacing != CameraMetadata.LENS_FACING_BACK) {
+ Log.v(TAG, "lensFacing=" + lensFacing + ". Crop-rotate-scale is disabled.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
- // Only enable the crop-rotate-scale workaround if the app targets M or below and is not
- // resizeable.
- if (!isMOrBelow(ctx, packageName) && taskInfo.isResizeable) {
- Slog.v(TAG,
- "The activity is N or above and claims to support resizeable-activity. "
- + "Crop-rotate-scale is disabled.");
- return false;
- }
+ // In case the activity behavior is not explicitly overridden, enable the
+ // crop-rotate-scale workaround if the app targets M (or below) or is not
+ // resizeable.
+ if (!ignoreResizableAndSdkCheck && !isMOrBelow(ctx, packageName) &&
+ taskInfo.isResizeable) {
+ Slog.v(TAG,
+ "The activity is N or above and claims to support resizeable-activity. "
+ + "Crop-rotate-scale is disabled.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
- DisplayManager displayManager = ctx.getSystemService(DisplayManager.class);
- Display display = displayManager.getDisplay(taskInfo.displayId);
- int rotation = display.getRotation();
- int rotationDegree = 0;
- switch (rotation) {
- case Surface.ROTATION_0:
- rotationDegree = 0;
- break;
- case Surface.ROTATION_90:
- rotationDegree = 90;
- break;
- case Surface.ROTATION_180:
- rotationDegree = 180;
- break;
- case Surface.ROTATION_270:
- rotationDegree = 270;
- break;
- }
+ if (!taskInfo.isFixedOrientationPortrait && !taskInfo.isFixedOrientationLandscape) {
+ Log.v(TAG, "Non-fixed orientation activity. Crop-rotate-scale is disabled.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
- // Here we only need to know whether the camera is landscape or portrait. Therefore we
- // don't need to consider whether it is a front or back camera. The formula works for
- // both.
- boolean landscapeCamera = ((rotationDegree + sensorOrientation) % 180 == 0);
- Slog.v(TAG,
- "Display.getRotation()=" + rotationDegree
- + " CameraCharacteristics.SENSOR_ORIENTATION=" + sensorOrientation
- + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
- + " isFixedOrientationLandscape=" +
- taskInfo.isFixedOrientationLandscape);
- // We need to do crop-rotate-scale when camera is landscape and activity is portrait or
- // vice versa.
- return (taskInfo.isFixedOrientationPortrait && landscapeCamera)
- || (taskInfo.isFixedOrientationLandscape && !landscapeCamera);
+ int rotationDegree;
+ switch (displayRotation) {
+ case Surface.ROTATION_0:
+ rotationDegree = 0;
+ break;
+ case Surface.ROTATION_90:
+ rotationDegree = 90;
+ break;
+ case Surface.ROTATION_180:
+ rotationDegree = 180;
+ break;
+ case Surface.ROTATION_270:
+ rotationDegree = 270;
+ break;
+ default:
+ Log.e(TAG, "Unsupported display rotation: " + displayRotation);
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ Slog.v(TAG,
+ "Display.getRotation()=" + rotationDegree
+ + " isFixedOrientationPortrait=" + taskInfo.isFixedOrientationPortrait
+ + " isFixedOrientationLandscape=" +
+ taskInfo.isFixedOrientationLandscape);
+ // We are trying to estimate the necessary rotation compensation for clients that
+ // don't handle various display orientations.
+ // The logic that is missing on client side is similar to the reference code
+ // in {@link android.hardware.Camera#setDisplayOrientation} where "info.orientation"
+ // is already applied in "CameraUtils::getRotationTransform".
+ // Care should be taken to reverse the rotation direction depending on the camera
+ // lens facing.
+ if (rotationDegree == 0) {
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+ if (lensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
+ // Switch direction for front facing cameras
+ rotationDegree = 360 - rotationDegree;
}
+ switch (rotationDegree) {
+ case 90:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_90;
+ case 270:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_270;
+ case 180:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_180;
+ case 0:
+ default:
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+ }
+
+ private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
@Override
- public boolean isRotateAndCropOverrideNeeded(String packageName, int sensorOrientation,
- int lensFacing) {
+ public int getRotateAndCropOverride(String packageName, int lensFacing, int userId) {
if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
" camera service UID!");
- return false;
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ TaskInfo taskInfo = null;
+ ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks = null;
+
+ try {
+ recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1,
+ /*flags*/ 0, userId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query recent tasks!");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ if ((recentTasks != null) && (!recentTasks.getList().isEmpty())) {
+ ActivityManager.RecentTaskInfo task = recentTasks.getList().get(0);
+ if (packageName.equals(task.topActivityInfo.packageName)) {
+ taskInfo = new TaskInfo();
+ taskInfo.frontTaskId = task.taskId;
+ taskInfo.isResizeable =
+ (task.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE);
+ taskInfo.displayId = task.displayId;
+ taskInfo.userId = task.userId;
+ taskInfo.isFixedOrientationLandscape =
+ ActivityInfo.isFixedOrientationLandscape(
+ task.topActivityInfo.screenOrientation);
+ taskInfo.isFixedOrientationPortrait =
+ ActivityInfo.isFixedOrientationPortrait(
+ task.topActivityInfo.screenOrientation);
+ } else {
+ Log.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName
+ + " doesn't match with camera client package name: " + packageName);
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+ } else {
+ Log.e(TAG, "Recent task list is empty!");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
// TODO: Modify the sensor orientation in camera characteristics along with any 3A
// regions in capture requests/results to account for thea physical rotation. The
// former is somewhat tricky as it assumes that camera clients always check for the
// current value by retrieving the camera characteristics from the camera device.
- return getNeedCropRotateScale(mContext, packageName,
- mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation,
- lensFacing);
+ if ((taskInfo != null) && (CompatChanges.isChangeEnabled(
+ OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName,
+ UserHandle.getUserHandleForUid(taskInfo.userId)))) {
+ Slog.v(TAG, "OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS enabled!");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+ boolean ignoreResizableAndSdkCheck = false;
+ if ((taskInfo != null) && (CompatChanges.isChangeEnabled(
+ OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK, packageName,
+ UserHandle.getUserHandleForUid(taskInfo.userId)))) {
+ Slog.v(TAG, "OVERRIDE_CAMERA_RESIZABLE_AND_SDK_CHECK enabled!");
+ ignoreResizableAndSdkCheck = true;
+ }
+
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ int displayRotation;
+ if (displayManager != null) {
+ Display display = displayManager.getDisplay(taskInfo.displayId);
+ if (display == null) {
+ Slog.e(TAG, "Invalid display id: " + taskInfo.displayId);
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ displayRotation = display.getRotation();
+ } else {
+ Slog.e(TAG, "Failed to query display manager!");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
+ return getCropRotateScale(mContext, packageName, taskInfo, displayRotation,
+ lensFacing, ignoreResizableAndSdkCheck);
}
@Override
@@ -447,6 +550,8 @@ public class CameraServiceProxy extends SystemService
}
};
+ private final FoldStateListener mFoldStateListener;
+
public CameraServiceProxy(Context context) {
super(context);
mContext = context;
@@ -459,6 +564,14 @@ public class CameraServiceProxy extends SystemService
// Don't keep any extra logging threads if not needed
mLogWriterService.setKeepAliveTime(1, TimeUnit.SECONDS);
mLogWriterService.allowCoreThreadTimeOut(true);
+
+ mFoldStateListener = new FoldStateListener(mContext, folded -> {
+ if (folded) {
+ setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
+ } else {
+ clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
+ }
+ });
}
/**
@@ -471,7 +584,7 @@ public class CameraServiceProxy extends SystemService
*
* @see #clearDeviceStateFlags(int)
*/
- public void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
+ private void setDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
synchronized (mLock) {
mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE);
mDeviceState |= deviceStateFlags;
@@ -491,7 +604,7 @@ public class CameraServiceProxy extends SystemService
*
* @see #setDeviceStateFlags(int)
*/
- public void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
+ private void clearDeviceStateFlags(@DeviceStateFlags int deviceStateFlags) {
synchronized (mLock) {
mHandler.removeMessages(MSG_NOTIFY_DEVICE_STATE);
mDeviceState &= ~deviceStateFlags;
@@ -544,12 +657,6 @@ public class CameraServiceProxy extends SystemService
CameraStatsJobService.schedule(mContext);
try {
- ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register task stack listener!");
- }
-
- try {
int[] displayIds = WindowManagerGlobal.getWindowManagerService()
.registerDisplayWindowListener(mDisplayWindowListener);
for (int i = 0; i < displayIds.length; i++) {
@@ -558,6 +665,9 @@ public class CameraServiceProxy extends SystemService
} catch (RemoteException e) {
Log.e(TAG, "Failed to register display window listener!");
}
+
+ mContext.getSystemService(DeviceStateManager.class)
+ .registerCallback(new HandlerExecutor(mHandler), mFoldStateListener);
}
}
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 0f97b9042ebe..b5846b555747 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -36,9 +36,9 @@ import com.android.server.compat.overrides.ChangeOverrides;
import com.android.server.compat.overrides.OverrideValue;
import com.android.server.compat.overrides.RawOverrideValue;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Represents the state of a single compatibility change.
@@ -82,8 +82,8 @@ public final class CompatChange extends CompatibilityChangeInfo {
ChangeListener mListener = null;
- private Map<String, Boolean> mEvaluatedOverrides;
- private Map<String, PackageOverride> mRawOverrides;
+ private ConcurrentHashMap<String, Boolean> mEvaluatedOverrides;
+ private ConcurrentHashMap<String, PackageOverride> mRawOverrides;
public CompatChange(long changeId) {
this(changeId, null, -1, -1, false, false, null, false);
@@ -114,11 +114,11 @@ public final class CompatChange extends CompatibilityChangeInfo {
description, overridable);
// Initialize override maps.
- mEvaluatedOverrides = new HashMap<>();
- mRawOverrides = new HashMap<>();
+ mEvaluatedOverrides = new ConcurrentHashMap<>();
+ mRawOverrides = new ConcurrentHashMap<>();
}
- void registerListener(ChangeListener listener) {
+ synchronized void registerListener(ChangeListener listener) {
if (mListener != null) {
throw new IllegalStateException(
"Listener for change " + toString() + " already registered.");
@@ -131,8 +131,6 @@ public final class CompatChange extends CompatibilityChangeInfo {
* Force the enabled state of this change for a given package name. The change will only take
* effect after that packages process is killed and restarted.
*
- * <p>Note, this method is not thread safe so callers must ensure thread safety.
- *
* @param pname Package name to enable the change for.
* @param enabled Whether or not to enable the change.
*/
@@ -155,14 +153,12 @@ public final class CompatChange extends CompatibilityChangeInfo {
* Tentatively set the state of this change for a given package name.
* The override will only take effect after that package is installed, if applicable.
*
- * <p>Note, this method is not thread safe so callers must ensure thread safety.
- *
* @param packageName Package name to tentatively enable the change for.
* @param override The package override to be set
* @param allowedState Whether the override is allowed.
* @param versionCode The version code of the package.
*/
- void addPackageOverride(String packageName, PackageOverride override,
+ synchronized void addPackageOverride(String packageName, PackageOverride override,
OverrideAllowedState allowedState, @Nullable Long versionCode) {
if (getLoggingOnly()) {
throw new IllegalArgumentException(
@@ -185,16 +181,17 @@ public final class CompatChange extends CompatibilityChangeInfo {
* @return {@code true} if the recheck yielded a result that requires invalidating caches
* (a deferred override was consolidated or a regular override was removed).
*/
- boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
+ synchronized boolean recheckOverride(String packageName, OverrideAllowedState allowedState,
@Nullable Long versionCode) {
+ if (packageName == null) {
+ return false;
+ }
boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED);
-
// If the app is not installed or no longer has raw overrides, evaluate to false
- if (versionCode == null || !hasRawOverride(packageName) || !allowed) {
+ if (versionCode == null || !mRawOverrides.containsKey(packageName) || !allowed) {
removePackageOverrideInternal(packageName);
return false;
}
-
// Evaluate the override based on its version
int overrideValue = mRawOverrides.get(packageName).evaluate(versionCode);
switch (overrideValue) {
@@ -211,10 +208,6 @@ public final class CompatChange extends CompatibilityChangeInfo {
return true;
}
- boolean hasPackageOverride(String pname) {
- return mRawOverrides.containsKey(pname);
- }
-
/**
* Remove any package override for the given package name, restoring the default behaviour.
*
@@ -224,9 +217,11 @@ public final class CompatChange extends CompatibilityChangeInfo {
* @param allowedState Whether the override is allowed.
* @param versionCode The version code of the package.
*/
- boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
+ synchronized boolean removePackageOverride(String pname, OverrideAllowedState allowedState,
@Nullable Long versionCode) {
- if (mRawOverrides.remove(pname) != null) {
+ if (mRawOverrides.containsKey(pname)) {
+ allowedState.enforce(getId(), pname);
+ mRawOverrides.remove(pname);
recheckOverride(pname, allowedState, versionCode);
return true;
}
@@ -244,8 +239,11 @@ public final class CompatChange extends CompatibilityChangeInfo {
if (app == null) {
return defaultValue();
}
- if (mEvaluatedOverrides.containsKey(app.packageName)) {
- return mEvaluatedOverrides.get(app.packageName);
+ if (app.packageName != null) {
+ final Boolean enabled = mEvaluatedOverrides.get(app.packageName);
+ if (enabled != null) {
+ return enabled;
+ }
}
if (getDisabled()) {
return false;
@@ -269,9 +267,12 @@ public final class CompatChange extends CompatibilityChangeInfo {
* @return {@code true} if the change should be enabled for the package.
*/
boolean willBeEnabled(String packageName) {
- if (hasRawOverride(packageName)) {
- int eval = mRawOverrides.get(packageName).evaluateForAllVersions();
- switch (eval) {
+ if (packageName == null) {
+ return defaultValue();
+ }
+ final PackageOverride override = mRawOverrides.get(packageName);
+ if (override != null) {
+ switch (override.evaluateForAllVersions()) {
case VALUE_ENABLED:
return true;
case VALUE_DISABLED:
@@ -292,30 +293,12 @@ public final class CompatChange extends CompatibilityChangeInfo {
return !getDisabled();
}
- /**
- * Checks whether a change has an override for a package.
- * @param packageName name of the package
- * @return true if there is such override
- */
- private boolean hasOverride(String packageName) {
- return mEvaluatedOverrides.containsKey(packageName);
- }
-
- /**
- * Checks whether a change has a deferred override for a package.
- * @param packageName name of the package
- * @return true if there is such a deferred override
- */
- private boolean hasRawOverride(String packageName) {
- return mRawOverrides.containsKey(packageName);
- }
-
- void clearOverrides() {
+ synchronized void clearOverrides() {
mRawOverrides.clear();
mEvaluatedOverrides.clear();
}
- void loadOverrides(ChangeOverrides changeOverrides) {
+ synchronized void loadOverrides(ChangeOverrides changeOverrides) {
// Load deferred overrides for backwards compatibility
if (changeOverrides.getDeferred() != null) {
for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) {
@@ -348,7 +331,7 @@ public final class CompatChange extends CompatibilityChangeInfo {
}
}
- ChangeOverrides saveOverrides() {
+ synchronized ChangeOverrides saveOverrides() {
if (mRawOverrides.isEmpty()) {
return null;
}
@@ -406,7 +389,7 @@ public final class CompatChange extends CompatibilityChangeInfo {
return sb.append(")").toString();
}
- private void notifyListener(String packageName) {
+ private synchronized void notifyListener(String packageName) {
if (mListener != null) {
mListener.onCompatChange(packageName);
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 3faffe198ac9..f2408cfa4124 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -28,7 +28,6 @@ import android.content.pm.PackageManager;
import android.os.Environment;
import android.text.TextUtils;
import android.util.LongArray;
-import android.util.LongSparseArray;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -55,11 +54,12 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.xml.datatype.DatatypeConfigurationException;
@@ -76,9 +76,7 @@ final class CompatConfig {
private static final String STATIC_OVERRIDES_PRODUCT_DIR = "/product/etc/appcompat";
private static final String OVERRIDES_FILE = "compat_framework_overrides.xml";
- private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
- @GuardedBy("mReadWriteLock")
- private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
+ private final ConcurrentHashMap<Long, CompatChange> mChanges = new ConcurrentHashMap<>();
private final OverrideValidatorImpl mOverrideValidator;
private final AndroidBuildClassifier mAndroidBuildClassifier;
@@ -113,21 +111,13 @@ final class CompatConfig {
/**
* Adds a change.
*
- * <p>This is intended to be used by code that reads change config from the filesystem. This
- * should be done at system startup time.
- *
- * <p>Any change with the same ID will be overwritten.
+ * <p>This is intended to be used by unit tests only.
*
* @param change the change to add
*/
+ @VisibleForTesting
void addChange(CompatChange change) {
- mReadWriteLock.writeLock().lock();
- try {
- mChanges.put(change.getId(), change);
- invalidateCache();
- } finally {
- mReadWriteLock.writeLock().unlock();
- }
+ mChanges.put(change.getId(), change);
}
/**
@@ -143,20 +133,14 @@ final class CompatConfig {
*/
long[] getDisabledChanges(ApplicationInfo app) {
LongArray disabled = new LongArray();
- mReadWriteLock.readLock().lock();
- try {
- for (int i = 0; i < mChanges.size(); ++i) {
- CompatChange c = mChanges.valueAt(i);
- if (!c.isEnabled(app, mAndroidBuildClassifier)) {
- disabled.add(c.getId());
- }
+ for (CompatChange c : mChanges.values()) {
+ if (!c.isEnabled(app, mAndroidBuildClassifier)) {
+ disabled.add(c.getId());
}
- } finally {
- mReadWriteLock.readLock().unlock();
}
- // Note: we don't need to explicitly sort the array, as the behaviour of LongSparseArray
- // (mChanges) ensures it's already sorted.
- return disabled.toArray();
+ final long[] sortedChanges = disabled.toArray();
+ Arrays.sort(sortedChanges);
+ return sortedChanges;
}
/**
@@ -166,15 +150,10 @@ final class CompatConfig {
* @return the change ID, or {@code -1} if no change with that name exists
*/
long lookupChangeId(String name) {
- mReadWriteLock.readLock().lock();
- try {
- for (int i = 0; i < mChanges.size(); ++i) {
- if (TextUtils.equals(mChanges.valueAt(i).getName(), name)) {
- return mChanges.keyAt(i);
- }
+ for (CompatChange c : mChanges.values()) {
+ if (TextUtils.equals(c.getName(), name)) {
+ return c.getId();
}
- } finally {
- mReadWriteLock.readLock().unlock();
}
return -1;
}
@@ -188,17 +167,12 @@ final class CompatConfig {
* change ID is not known, as unknown changes are enabled by default.
*/
boolean isChangeEnabled(long changeId, ApplicationInfo app) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- if (c == null) {
- // we know nothing about this change: default behaviour is enabled.
- return true;
- }
- return c.isEnabled(app, mAndroidBuildClassifier);
- } finally {
- mReadWriteLock.readLock().unlock();
+ CompatChange c = mChanges.get(changeId);
+ if (c == null) {
+ // we know nothing about this change: default behaviour is enabled.
+ return true;
}
+ return c.isEnabled(app, mAndroidBuildClassifier);
}
/**
@@ -210,17 +184,12 @@ final class CompatConfig {
* {@code true} if the change ID is not known, as unknown changes are enabled by default.
*/
boolean willChangeBeEnabled(long changeId, String packageName) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- if (c == null) {
- // we know nothing about this change: default behaviour is enabled.
- return true;
- }
- return c.willBeEnabled(packageName);
- } finally {
- mReadWriteLock.readLock().unlock();
+ CompatChange c = mChanges.get(changeId);
+ if (c == null) {
+ // we know nothing about this change: default behaviour is enabled.
+ return true;
}
+ return c.willBeEnabled(packageName);
}
/**
@@ -239,7 +208,7 @@ final class CompatConfig {
* @return {@code true} if the change existed before adding the override
* @throws IllegalStateException if overriding is not allowed
*/
- boolean addOverride(long changeId, String packageName, boolean enabled) {
+ synchronized boolean addOverride(long changeId, String packageName, boolean enabled) {
boolean alreadyKnown = addOverrideUnsafe(changeId, packageName,
new PackageOverride.Builder().setEnabled(enabled).build());
saveOverrides();
@@ -250,13 +219,19 @@ final class CompatConfig {
/**
* Overrides the enabled state for a given change and app.
*
- * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
*
- * @param overrides list of overrides to default changes config.
- * @param packageName app for which the overrides will be applied.
+ * @param overrides list of overrides to default changes config.
+ * @param packageName app for which the overrides will be applied.
+ * @param skipUnknownChangeIds whether to skip unknown change IDs in {@code overrides}.
*/
- void addOverrides(CompatibilityOverrideConfig overrides, String packageName) {
+ synchronized void addPackageOverrides(CompatibilityOverrideConfig overrides,
+ String packageName, boolean skipUnknownChangeIds) {
for (Long changeId : overrides.overrides.keySet()) {
+ if (skipUnknownChangeIds && !isKnownChangeId(changeId)) {
+ Slog.w(TAG, "Trying to add overrides for unknown Change ID " + changeId + ". "
+ + "Skipping Change ID.");
+ continue;
+ }
addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId));
}
saveOverrides();
@@ -265,36 +240,24 @@ final class CompatConfig {
private boolean addOverrideUnsafe(long changeId, String packageName,
PackageOverride overrides) {
- boolean alreadyKnown = true;
+ final AtomicBoolean alreadyKnown = new AtomicBoolean(true);
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
allowedState.enforce(changeId, packageName);
Long versionCode = getVersionCodeOrNull(packageName);
- mReadWriteLock.writeLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- if (c == null) {
- alreadyKnown = false;
- c = new CompatChange(changeId);
- addChange(c);
- }
- c.addPackageOverride(packageName, overrides, allowedState, versionCode);
- invalidateCache();
- } finally {
- mReadWriteLock.writeLock().unlock();
- }
- return alreadyKnown;
+
+ final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> {
+ alreadyKnown.set(false);
+ return new CompatChange(changeId);
+ });
+ c.addPackageOverride(packageName, overrides, allowedState, versionCode);
+ invalidateCache();
+ return alreadyKnown.get();
}
/** Checks whether the change is known to the compat config. */
boolean isKnownChangeId(long changeId) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- return c != null;
- } finally {
- mReadWriteLock.readLock().unlock();
- }
+ return mChanges.containsKey(changeId);
}
/**
@@ -302,55 +265,35 @@ final class CompatConfig {
* target SDK gated).
*/
int maxTargetSdkForChangeIdOptIn(long changeId) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- if (c != null && c.getEnableSinceTargetSdk() != -1) {
- return c.getEnableSinceTargetSdk() - 1;
- }
- return -1;
- } finally {
- mReadWriteLock.readLock().unlock();
+ CompatChange c = mChanges.get(changeId);
+ if (c != null && c.getEnableSinceTargetSdk() != -1) {
+ return c.getEnableSinceTargetSdk() - 1;
}
+ return -1;
}
/**
* Returns whether the change is marked as logging only.
*/
boolean isLoggingOnly(long changeId) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- return c != null && c.getLoggingOnly();
- } finally {
- mReadWriteLock.readLock().unlock();
- }
+ CompatChange c = mChanges.get(changeId);
+ return c != null && c.getLoggingOnly();
}
/**
* Returns whether the change is marked as disabled.
*/
boolean isDisabled(long changeId) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- return c != null && c.getDisabled();
- } finally {
- mReadWriteLock.readLock().unlock();
- }
+ CompatChange c = mChanges.get(changeId);
+ return c != null && c.getDisabled();
}
/**
* Returns whether the change is overridable.
*/
boolean isOverridable(long changeId) {
- mReadWriteLock.readLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- return c != null && c.getOverridable();
- } finally {
- mReadWriteLock.readLock().unlock();
- }
+ CompatChange c = mChanges.get(changeId);
+ return c != null && c.getOverridable();
}
/**
@@ -363,10 +306,12 @@ final class CompatConfig {
* @param packageName the app package name that was overridden
* @return {@code true} if an override existed;
*/
- boolean removeOverride(long changeId, String packageName) {
+ synchronized boolean removeOverride(long changeId, String packageName) {
boolean overrideExists = removeOverrideUnsafe(changeId, packageName);
- saveOverrides();
- invalidateCache();
+ if (overrideExists) {
+ saveOverrides();
+ invalidateCache();
+ }
return overrideExists;
}
@@ -376,14 +321,9 @@ final class CompatConfig {
*/
private boolean removeOverrideUnsafe(long changeId, String packageName) {
Long versionCode = getVersionCodeOrNull(packageName);
- mReadWriteLock.writeLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- if (c != null) {
- return removeOverrideUnsafe(c, packageName, versionCode);
- }
- } finally {
- mReadWriteLock.writeLock().unlock();
+ CompatChange c = mChanges.get(changeId);
+ if (c != null) {
+ return removeOverrideUnsafe(c, packageName, versionCode);
}
return false;
}
@@ -397,76 +337,71 @@ final class CompatConfig {
long changeId = change.getId();
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
- if (change.hasPackageOverride(packageName)) {
- allowedState.enforce(changeId, packageName);
- change.removePackageOverride(packageName, allowedState, versionCode);
- invalidateCache();
- return true;
- }
- return false;
+ return change.removePackageOverride(packageName, allowedState, versionCode);
}
/**
* Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
- * {@link #addOverrides(CompatibilityOverrideConfig, String)} for a certain package.
+ * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain
+ * package.
*
* <p>This restores the default behaviour for the given app.
*
* @param packageName the package for which the overrides should be purged
*/
- void removePackageOverrides(String packageName) {
+ synchronized void removePackageOverrides(String packageName) {
Long versionCode = getVersionCodeOrNull(packageName);
- mReadWriteLock.writeLock().lock();
- try {
- for (int i = 0; i < mChanges.size(); ++i) {
- CompatChange change = mChanges.valueAt(i);
- removeOverrideUnsafe(change, packageName, versionCode);
- }
- } finally {
- mReadWriteLock.writeLock().unlock();
+ boolean shouldInvalidateCache = false;
+ for (CompatChange change : mChanges.values()) {
+ shouldInvalidateCache |= removeOverrideUnsafe(change, packageName, versionCode);
+ }
+ if (shouldInvalidateCache) {
+ saveOverrides();
+ invalidateCache();
}
- saveOverrides();
- invalidateCache();
}
/**
* Removes overrides whose change ID is specified in {@code overridesToRemove} that were
* previously added via {@link #addOverride(long, String, boolean)} or
- * {@link #addOverrides(CompatibilityOverrideConfig, String)} for a certain package.
+ * {@link #addPackageOverrides(CompatibilityOverrideConfig, String, boolean)} for a certain
+ * package.
*
* <p>This restores the default behaviour for the given change IDs and app.
*
* @param overridesToRemove list of change IDs for which to restore the default behaviour.
* @param packageName the package for which the overrides should be purged
*/
- void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
+ synchronized void removePackageOverrides(CompatibilityOverridesToRemoveConfig overridesToRemove,
String packageName) {
+ boolean shouldInvalidateCache = false;
for (Long changeId : overridesToRemove.changeIds) {
- removeOverrideUnsafe(changeId, packageName);
+ if (!isKnownChangeId(changeId)) {
+ Slog.w(TAG, "Trying to remove overrides for unknown Change ID " + changeId + ". "
+ + "Skipping Change ID.");
+ continue;
+ }
+ shouldInvalidateCache |= removeOverrideUnsafe(changeId, packageName);
+ }
+ if (shouldInvalidateCache) {
+ saveOverrides();
+ invalidateCache();
}
- saveOverrides();
- invalidateCache();
}
private long[] getAllowedChangesSinceTargetSdkForPackage(String packageName,
int targetSdkVersion) {
LongArray allowed = new LongArray();
- mReadWriteLock.readLock().lock();
- try {
- for (int i = 0; i < mChanges.size(); ++i) {
- CompatChange change = mChanges.valueAt(i);
- if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
- continue;
- }
- OverrideAllowedState allowedState =
- mOverrideValidator.getOverrideAllowedState(change.getId(),
- packageName);
- if (allowedState.state == OverrideAllowedState.ALLOWED) {
- allowed.add(change.getId());
- }
+ for (CompatChange change : mChanges.values()) {
+ if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
+ continue;
+ }
+ OverrideAllowedState allowedState =
+ mOverrideValidator.getOverrideAllowedState(change.getId(),
+ packageName);
+ if (allowedState.state == OverrideAllowedState.ALLOWED) {
+ allowed.add(change.getId());
}
- } finally {
- mReadWriteLock.readLock().unlock();
}
return allowed.toArray();
}
@@ -479,12 +414,15 @@ final class CompatConfig {
*/
int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
+ boolean shouldInvalidateCache = false;
for (long changeId : changes) {
- addOverrideUnsafe(changeId, packageName,
+ shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName,
new PackageOverride.Builder().setEnabled(true).build());
}
- saveOverrides();
- invalidateCache();
+ if (shouldInvalidateCache) {
+ saveOverrides();
+ invalidateCache();
+ }
return changes.length;
}
@@ -496,30 +434,27 @@ final class CompatConfig {
*/
int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) {
long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion);
+ boolean shouldInvalidateCache = false;
for (long changeId : changes) {
- addOverrideUnsafe(changeId, packageName,
+ shouldInvalidateCache |= addOverrideUnsafe(changeId, packageName,
new PackageOverride.Builder().setEnabled(false).build());
}
- saveOverrides();
- invalidateCache();
+ if (shouldInvalidateCache) {
+ saveOverrides();
+ invalidateCache();
+ }
return changes.length;
}
boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
- boolean alreadyKnown = true;
- mReadWriteLock.writeLock().lock();
- try {
- CompatChange c = mChanges.get(changeId);
- if (c == null) {
- alreadyKnown = false;
- c = new CompatChange(changeId);
- addChange(c);
- }
- c.registerListener(listener);
- } finally {
- mReadWriteLock.writeLock().unlock();
- }
- return alreadyKnown;
+ final AtomicBoolean alreadyKnown = new AtomicBoolean(true);
+ final CompatChange c = mChanges.computeIfAbsent(changeId, (key) -> {
+ alreadyKnown.set(false);
+ invalidateCache();
+ return new CompatChange(changeId);
+ });
+ c.registerListener(listener);
+ return alreadyKnown.get();
}
boolean defaultChangeIdValue(long changeId) {
@@ -537,12 +472,7 @@ final class CompatConfig {
@VisibleForTesting
void clearChanges() {
- mReadWriteLock.writeLock().lock();
- try {
- mChanges.clear();
- } finally {
- mReadWriteLock.writeLock().unlock();
- }
+ mChanges.clear();
}
/**
@@ -551,18 +481,12 @@ final class CompatConfig {
* @param pw {@link PrintWriter} instance to which the information will be dumped
*/
void dumpConfig(PrintWriter pw) {
- mReadWriteLock.readLock().lock();
- try {
- if (mChanges.size() == 0) {
- pw.println("No compat overrides.");
- return;
- }
- for (int i = 0; i < mChanges.size(); ++i) {
- CompatChange c = mChanges.valueAt(i);
- pw.println(c.toString());
- }
- } finally {
- mReadWriteLock.readLock().unlock();
+ if (mChanges.size() == 0) {
+ pw.println("No compat overrides.");
+ return;
+ }
+ for (CompatChange c : mChanges.values()) {
+ pw.println(c.toString());
}
}
@@ -574,18 +498,12 @@ final class CompatConfig {
CompatibilityChangeConfig getAppConfig(ApplicationInfo applicationInfo) {
Set<Long> enabled = new HashSet<>();
Set<Long> disabled = new HashSet<>();
- mReadWriteLock.readLock().lock();
- try {
- for (int i = 0; i < mChanges.size(); ++i) {
- CompatChange c = mChanges.valueAt(i);
- if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) {
- enabled.add(c.getId());
- } else {
- disabled.add(c.getId());
- }
+ for (CompatChange c : mChanges.values()) {
+ if (c.isEnabled(applicationInfo, mAndroidBuildClassifier)) {
+ enabled.add(c.getId());
+ } else {
+ disabled.add(c.getId());
}
- } finally {
- mReadWriteLock.readLock().unlock();
}
return new CompatibilityChangeConfig(new ChangeConfig(enabled, disabled));
}
@@ -596,17 +514,12 @@ final class CompatConfig {
* @return an array of {@link CompatibilityChangeInfo} with the current changes
*/
CompatibilityChangeInfo[] dumpChanges() {
- mReadWriteLock.readLock().lock();
- try {
- CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()];
- for (int i = 0; i < mChanges.size(); ++i) {
- CompatChange change = mChanges.valueAt(i);
- changeInfos[i] = new CompatibilityChangeInfo(change);
- }
- return changeInfos;
- } finally {
- mReadWriteLock.readLock().unlock();
+ CompatibilityChangeInfo[] changeInfos = new CompatibilityChangeInfo[mChanges.size()];
+ int i = 0;
+ for (CompatChange change : mChanges.values()) {
+ changeInfos[i++] = new CompatibilityChangeInfo(change);
}
+ return changeInfos;
}
void initConfigFromLib(File libraryDir) {
@@ -626,10 +539,12 @@ final class CompatConfig {
Config config = com.android.server.compat.config.XmlParser.read(in);
for (Change change : config.getCompatChange()) {
Slog.d(TAG, "Adding: " + change.toString());
- addChange(new CompatChange(change));
+ mChanges.put(change.getId(), new CompatChange(change));
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
Slog.e(TAG, "Encountered an error while reading/parsing compat config file", e);
+ } finally {
+ invalidateCache();
}
}
@@ -641,15 +556,12 @@ final class CompatConfig {
@VisibleForTesting
void initOverrides(File dynamicOverridesFile, File staticOverridesFile) {
// Clear overrides from all changes before loading.
- mReadWriteLock.writeLock().lock();
- try {
- for (int i = 0; i < mChanges.size(); ++i) {
- mChanges.valueAt(i).clearOverrides();
- }
- } finally {
- mReadWriteLock.writeLock().unlock();
+
+ for (CompatChange c : mChanges.values()) {
+ c.clearOverrides();
}
+
loadOverrides(staticOverridesFile);
mOverridesFile = dynamicOverridesFile;
@@ -698,18 +610,12 @@ final class CompatConfig {
}
synchronized (mOverridesFile) {
Overrides overrides = new Overrides();
- mReadWriteLock.readLock().lock();
- try {
- List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
- for (int idx = 0; idx < mChanges.size(); ++idx) {
- CompatChange c = mChanges.valueAt(idx);
- ChangeOverrides changeOverrides = c.saveOverrides();
- if (changeOverrides != null) {
- changeOverridesList.add(changeOverrides);
- }
+ List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides();
+ for (CompatChange c : mChanges.values()) {
+ ChangeOverrides changeOverrides = c.saveOverrides();
+ if (changeOverrides != null) {
+ changeOverridesList.add(changeOverrides);
}
- } finally {
- mReadWriteLock.readLock().unlock();
}
// Create the file if it doesn't already exist
try {
@@ -741,20 +647,11 @@ final class CompatConfig {
void recheckOverrides(String packageName) {
Long versionCode = getVersionCodeOrNull(packageName);
boolean shouldInvalidateCache = false;
- mReadWriteLock.readLock().lock();
- try {
- for (int idx = 0; idx < mChanges.size(); ++idx) {
- CompatChange c = mChanges.valueAt(idx);
- if (!c.hasPackageOverride(packageName)) {
- continue;
- }
- OverrideAllowedState allowedState =
- mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(),
- packageName);
- shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode);
- }
- } finally {
- mReadWriteLock.readLock().unlock();
+ for (CompatChange c : mChanges.values()) {
+ OverrideAllowedState allowedState =
+ mOverrideValidator.getOverrideAllowedStateForRecheck(c.getId(),
+ packageName);
+ shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, versionCode);
}
if (shouldInvalidateCache) {
invalidateCache();
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index b32d1d749680..6ea89d445402 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -205,7 +205,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
.build());
}
- mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
+ mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap),
+ packageName, /* skipUnknownChangeIds */ false);
killPackage(packageName);
}
@@ -220,7 +221,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
overridesMap.put(change, new PackageOverride.Builder().setEnabled(false)
.build());
}
- mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName);
+ mCompatConfig.addPackageOverrides(new CompatibilityOverrideConfig(overridesMap),
+ packageName, /* skipUnknownChangeIds */ false);
}
@Override
@@ -229,7 +231,7 @@ public class PlatformCompat extends IPlatformCompat.Stub {
// TODO(b/183630314): Unify the permission enforcement with the other setOverrides* methods.
checkCompatChangeOverrideOverridablePermission();
checkAllCompatOverridesAreOverridable(overrides.overrides.keySet());
- mCompatConfig.addOverrides(overrides, packageName);
+ mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true);
}
@Override
@@ -435,7 +437,7 @@ public class PlatformCompat extends IPlatformCompat.Stub {
private void checkAllCompatOverridesAreOverridable(Collection<Long> changeIds) {
for (Long changeId : changeIds) {
- if (!mCompatConfig.isOverridable(changeId)) {
+ if (isKnownChangeId(changeId) && !mCompatConfig.isOverridable(changeId)) {
throw new SecurityException("Only change ids marked as Overridable can be "
+ "overridden.");
}
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
new file mode 100644
index 000000000000..11dc1dbd25cc
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesParser.java
@@ -0,0 +1,337 @@
+/*
+ * 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.compat.overrides;
+
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.emptySet;
+
+import android.app.compat.PackageOverride;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * A utility class for parsing App Compat Overrides flags.
+ *
+ * @hide
+ */
+final class AppCompatOverridesParser {
+ /**
+ * Flag for specifying all compat change IDs owned by a namespace. See {@link
+ * #parseOwnedChangeIds} for information on how this flag is parsed.
+ */
+ static final String FLAG_OWNED_CHANGE_IDS = "owned_change_ids";
+
+ /**
+ * Flag for immediately removing overrides for certain packages and change IDs (from the compat
+ * platform), as well as stopping to apply them, in case of an emergency. See {@link
+ * #parseRemoveOverrides} for information on how this flag is parsed.
+ */
+ static final String FLAG_REMOVE_OVERRIDES = "remove_overrides";
+
+ private static final String TAG = "AppCompatOverridesParser";
+
+ private static final String WILDCARD_SYMBOL = "*";
+
+ private static final Pattern BOOLEAN_PATTERN =
+ Pattern.compile("true|false", Pattern.CASE_INSENSITIVE);
+
+ private static final String WILDCARD_NO_OWNED_CHANGE_IDS_WARNING =
+ "Wildcard can't be used in '" + FLAG_REMOVE_OVERRIDES + "' flag with an empty "
+ + FLAG_OWNED_CHANGE_IDS + "' flag";
+
+ private final PackageManager mPackageManager;
+
+ AppCompatOverridesParser(PackageManager packageManager) {
+ mPackageManager = packageManager;
+ }
+
+ /**
+ * Parses the given {@code configStr} and returns a map from package name to a set of change
+ * IDs to remove for that package.
+ *
+ * <p>The given {@code configStr} is expected to either be:
+ *
+ * <ul>
+ * <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code
+ * ownedChangeIds}, for all installed packages should be removed.
+ * <li>A comma separated key value list, where the key is a package name and the value is
+ * either:
+ * <ul>
+ * <li>'*' (wildcard), to indicate that all owned overrides, specified in {@code
+ * ownedChangeIds} for that package should be removed.
+ * <li>A colon separated list of change IDs to remove for that package.
+ * </ul>
+ * </ul>
+ *
+ * <p>If the given {@code configStr} doesn't match the expected format, an empty map will be
+ * returned. If a specific change ID isn't a valid long, it will be ignored.
+ */
+ Map<String, Set<Long>> parseRemoveOverrides(String configStr, Set<Long> ownedChangeIds) {
+ if (configStr.isEmpty()) {
+ return emptyMap();
+ }
+
+ Map<String, Set<Long>> result = new ArrayMap<>();
+ if (configStr.equals(WILDCARD_SYMBOL)) {
+ if (ownedChangeIds.isEmpty()) {
+ Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING);
+ return emptyMap();
+ }
+ List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
+ MATCH_ANY_USER);
+ for (ApplicationInfo appInfo : installedApps) {
+ result.put(appInfo.packageName, ownedChangeIds);
+ }
+ return result;
+ }
+
+ KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(configStr);
+ } catch (IllegalArgumentException e) {
+ Slog.w(
+ TAG,
+ "Invalid format in '" + FLAG_REMOVE_OVERRIDES + "' flag: " + configStr, e);
+ return emptyMap();
+ }
+ for (int i = 0; i < parser.size(); i++) {
+ String packageName = parser.keyAt(i);
+ String changeIdsStr = parser.getString(packageName, /* def= */ "");
+ if (changeIdsStr.equals(WILDCARD_SYMBOL)) {
+ if (ownedChangeIds.isEmpty()) {
+ Slog.w(TAG, WILDCARD_NO_OWNED_CHANGE_IDS_WARNING);
+ continue;
+ }
+ result.put(packageName, ownedChangeIds);
+ } else {
+ for (String changeIdStr : changeIdsStr.split(":")) {
+ try {
+ long changeId = Long.parseLong(changeIdStr);
+ result.computeIfAbsent(packageName, k -> new ArraySet<>()).add(changeId);
+ } catch (NumberFormatException e) {
+ Slog.w(
+ TAG,
+ "Invalid change ID in '" + FLAG_REMOVE_OVERRIDES + "' flag: "
+ + changeIdStr, e);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Parses the given {@code configStr}, that is expected to be a comma separated list of change
+ * IDs, into a set.
+ *
+ * <p>If any of the change IDs isn't a valid long, it will be ignored.
+ */
+ static Set<Long> parseOwnedChangeIds(String configStr) {
+ if (configStr.isEmpty()) {
+ return emptySet();
+ }
+
+ Set<Long> result = new ArraySet<>();
+ for (String changeIdStr : configStr.split(",")) {
+ try {
+ result.add(Long.parseLong(changeIdStr));
+ } catch (NumberFormatException e) {
+ Slog.w(TAG,
+ "Invalid change ID in '" + FLAG_OWNED_CHANGE_IDS + "' flag: " + changeIdStr,
+ e);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Parses the given {@code configStr}, that is expected to be a comma separated list of changes
+ * overrides, and returns a map from change ID to {@link PackageOverride} instances to add.
+ *
+ * <p>Each change override is in the following format:
+ * '<change-id>:<min-version-code?>:<max-version-code?>:<enabled>'.
+ *
+ * <p>If there are multiple overrides that should be added with the same change ID, the one
+ * that best fits the given {@code versionCode} is added.
+ *
+ * <p>Any overrides whose change ID is in {@code changeIdsToSkip} are ignored.
+ *
+ * <p>If a change override entry in {@code configStr} is invalid, it will be ignored.
+ */
+ static Map<Long, PackageOverride> parsePackageOverrides(String configStr, long versionCode,
+ Set<Long> changeIdsToSkip) {
+ if (configStr.isEmpty()) {
+ return emptyMap();
+ }
+ PackageOverrideComparator comparator = new PackageOverrideComparator(versionCode);
+ Map<Long, PackageOverride> overridesToAdd = new ArrayMap<>();
+ for (String overrideEntryString : configStr.split(",")) {
+ List<String> changeIdAndVersions = Arrays.asList(overrideEntryString.split(":", 4));
+ if (changeIdAndVersions.size() != 4) {
+ Slog.w(TAG, "Invalid change override entry: " + overrideEntryString);
+ continue;
+ }
+ long changeId;
+ try {
+ changeId = Long.parseLong(changeIdAndVersions.get(0));
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Invalid change ID in override entry: " + overrideEntryString, e);
+ continue;
+ }
+
+ if (changeIdsToSkip.contains(changeId)) {
+ continue;
+ }
+
+ String minVersionCodeStr = changeIdAndVersions.get(1);
+ String maxVersionCodeStr = changeIdAndVersions.get(2);
+
+ String enabledStr = changeIdAndVersions.get(3);
+ if (!BOOLEAN_PATTERN.matcher(enabledStr).matches()) {
+ Slog.w(TAG, "Invalid enabled string in override entry: " + overrideEntryString);
+ continue;
+ }
+ boolean enabled = Boolean.parseBoolean(enabledStr);
+ PackageOverride.Builder overrideBuilder = new PackageOverride.Builder().setEnabled(
+ enabled);
+ try {
+ if (!minVersionCodeStr.isEmpty()) {
+ overrideBuilder.setMinVersionCode(Long.parseLong(minVersionCodeStr));
+ }
+ if (!maxVersionCodeStr.isEmpty()) {
+ overrideBuilder.setMaxVersionCode(Long.parseLong(maxVersionCodeStr));
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG,
+ "Invalid min/max version code in override entry: " + overrideEntryString,
+ e);
+ continue;
+ }
+
+ try {
+ PackageOverride override = overrideBuilder.build();
+ if (!overridesToAdd.containsKey(changeId)
+ || comparator.compare(override, overridesToAdd.get(changeId)) < 0) {
+ overridesToAdd.put(changeId, override);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed to build PackageOverride", e);
+ }
+ }
+
+ return overridesToAdd;
+ }
+
+ /**
+ * A {@link Comparator} that compares @link PackageOverride} instances with respect to a
+ * specified {@code versionCode} as follows:
+ *
+ * <ul>
+ * <li>Prefer the {@link PackageOverride} whose version range contains {@code versionCode}.
+ * <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code
+ * versionCode} from below.
+ * <li>Otherwise, prefer the {@link PackageOverride} whose version range is closest to {@code
+ * versionCode} from above.
+ * </ul>
+ */
+ private static final class PackageOverrideComparator implements Comparator<PackageOverride> {
+ private final long mVersionCode;
+
+ PackageOverrideComparator(long versionCode) {
+ this.mVersionCode = versionCode;
+ }
+
+ @Override
+ public int compare(PackageOverride o1, PackageOverride o2) {
+ // Prefer overrides whose version range contains versionCode.
+ boolean isVersionInRange1 = isVersionInRange(o1, mVersionCode);
+ boolean isVersionInRange2 = isVersionInRange(o2, mVersionCode);
+ if (isVersionInRange1 != isVersionInRange2) {
+ return isVersionInRange1 ? -1 : 1;
+ }
+
+ // Otherwise, prefer overrides whose version range is before versionCode.
+ boolean isVersionAfterRange1 = isVersionAfterRange(o1, mVersionCode);
+ boolean isVersionAfterRange2 = isVersionAfterRange(o2, mVersionCode);
+ if (isVersionAfterRange1 != isVersionAfterRange2) {
+ return isVersionAfterRange1 ? -1 : 1;
+ }
+
+ // If both overrides' version ranges are either before or after versionCode, prefer
+ // those whose version range is closer to versionCode.
+ return Long.compare(
+ getVersionProximity(o1, mVersionCode), getVersionProximity(o2, mVersionCode));
+ }
+
+ /**
+ * Returns true if the version range in the given {@code override} contains {@code
+ * versionCode}.
+ */
+ private static boolean isVersionInRange(PackageOverride override, long versionCode) {
+ return override.getMinVersionCode() <= versionCode
+ && versionCode <= override.getMaxVersionCode();
+ }
+
+ /**
+ * Returns true if the given {@code versionCode} is strictly after the version range in the
+ * given {@code override}.
+ */
+ private static boolean isVersionAfterRange(PackageOverride override, long versionCode) {
+ return override.getMaxVersionCode() < versionCode;
+ }
+
+ /**
+ * Returns true if the given {@code versionCode} is strictly before the version range in the
+ * given {@code override}.
+ */
+ private static boolean isVersionBeforeRange(PackageOverride override, long versionCode) {
+ return override.getMinVersionCode() > versionCode;
+ }
+
+ /**
+ * In case the given {@code versionCode} is strictly before or after the version range in
+ * the given {@code override}, returns the distance from it, otherwise returns zero.
+ */
+ private static long getVersionProximity(PackageOverride override, long versionCode) {
+ if (isVersionAfterRange(override, versionCode)) {
+ return versionCode - override.getMaxVersionCode();
+ }
+ if (isVersionBeforeRange(override, versionCode)) {
+ return override.getMinVersionCode() - versionCode;
+ }
+
+ // Version is in range. Note that when two overrides have a zero version proximity
+ // they will be ordered arbitrarily.
+ return 0;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
new file mode 100644
index 000000000000..6aed4b023297
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/AppCompatOverridesService.java
@@ -0,0 +1,430 @@
+/*
+ * 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.compat.overrides;
+
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
+import static android.provider.DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES;
+
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
+
+import static java.util.Collections.emptySet;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.compat.PackageOverride;
+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.PackageManager;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Service for applying per-app compat overrides delivered via Device Config.
+ *
+ * <p>The service listens both on changes to supported Device Config namespaces and on package
+ * added/changed/removed events, and applies overrides accordingly.
+ *
+ * @hide
+ */
+public final class AppCompatOverridesService {
+ private static final String TAG = "AppCompatOverridesService";
+
+ private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(
+ NAMESPACE_APP_COMPAT_OVERRIDES);
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final IPlatformCompat mPlatformCompat;
+ private final List<String> mSupportedNamespaces;
+ private final AppCompatOverridesParser mOverridesParser;
+ private final PackageReceiver mPackageReceiver;
+ private final List<DeviceConfigListener> mDeviceConfigListeners;
+
+ private AppCompatOverridesService(Context context) {
+ this(context, IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)), SUPPORTED_NAMESPACES);
+ }
+
+ @VisibleForTesting
+ AppCompatOverridesService(Context context, IPlatformCompat platformCompat,
+ List<String> supportedNamespaces) {
+ mContext = context;
+ mPackageManager = mContext.getPackageManager();
+ mPlatformCompat = platformCompat;
+ mSupportedNamespaces = supportedNamespaces;
+ mOverridesParser = new AppCompatOverridesParser(mPackageManager);
+ mPackageReceiver = new PackageReceiver(mContext);
+ mDeviceConfigListeners = new ArrayList<>();
+ for (String namespace : mSupportedNamespaces) {
+ mDeviceConfigListeners.add(new DeviceConfigListener(mContext, namespace));
+ }
+ }
+
+ @Override
+ public void finalize() {
+ unregisterDeviceConfigListeners();
+ unregisterPackageReceiver();
+ }
+
+ @VisibleForTesting
+ void registerDeviceConfigListeners() {
+ for (DeviceConfigListener listener : mDeviceConfigListeners) {
+ listener.register();
+ }
+ }
+
+ private void unregisterDeviceConfigListeners() {
+ for (DeviceConfigListener listener : mDeviceConfigListeners) {
+ listener.unregister();
+ }
+ }
+
+ @VisibleForTesting
+ void registerPackageReceiver() {
+ mPackageReceiver.register();
+ }
+
+ private void unregisterPackageReceiver() {
+ mPackageReceiver.unregister();
+ }
+
+ /**
+ * Same as {@link #applyOverrides(Properties, Set, Map)} except all properties of the given
+ * {@code namespace} are fetched via {@link DeviceConfig#getProperties}.
+ */
+ private void applyAllOverrides(String namespace, Set<Long> ownedChangeIds,
+ Map<String, Set<Long>> packageToChangeIdsToSkip) {
+ applyOverrides(DeviceConfig.getProperties(namespace), ownedChangeIds,
+ packageToChangeIdsToSkip);
+ }
+
+ /**
+ * Iterates all package override flags in the given {@code properties}, and for each flag whose
+ * package is installed on the device, parses its value and adds the overrides in it with
+ * respect to the package's current installed version.
+ *
+ * <p>In addition, for each package, removes any override that wasn't just added, whose change
+ * ID is in {@code ownedChangeIds} but not in the respective set in {@code
+ * packageToChangeIdsToSkip}.
+ */
+ private void applyOverrides(Properties properties, Set<Long> ownedChangeIds,
+ Map<String, Set<Long>> packageToChangeIdsToSkip) {
+ Set<String> packageNames = new ArraySet<>(properties.getKeyset());
+ packageNames.remove(FLAG_OWNED_CHANGE_IDS);
+ packageNames.remove(FLAG_REMOVE_OVERRIDES);
+ for (String packageName : packageNames) {
+ Long versionCode = getVersionCodeOrNull(packageName);
+ if (versionCode == null) {
+ // Package isn't installed yet.
+ continue;
+ }
+
+ applyPackageOverrides(properties.getString(packageName, /* defaultValue= */ ""),
+ packageName, versionCode, ownedChangeIds,
+ packageToChangeIdsToSkip.getOrDefault(packageName, emptySet()),
+ /* removeOtherOwnedOverrides= */ true);
+ }
+ }
+
+ /**
+ * Adds all overrides in all supported namespaces for the given {@code packageName}.
+ */
+ private void addAllPackageOverrides(String packageName) {
+ Long versionCode = getVersionCodeOrNull(packageName);
+ if (versionCode == null) {
+ return;
+ }
+
+ for (String namespace : mSupportedNamespaces) {
+ // We apply overrides for each namespace separately so that if there is a failure for
+ // one namespace, the other namespaces won't be affected.
+ Set<Long> ownedChangeIds = getOwnedChangeIds(namespace);
+ applyPackageOverrides(
+ DeviceConfig.getString(namespace, packageName, /* defaultValue= */ ""),
+ packageName, versionCode, ownedChangeIds,
+ getOverridesToRemove(namespace, ownedChangeIds).getOrDefault(packageName,
+ emptySet()), /* removeOtherOwnedOverrides */ false);
+ }
+ }
+
+ /**
+ * Calls {@link AppCompatOverridesParser#parsePackageOverrides} on the given arguments and adds
+ * the resulting overrides via {@link IPlatformCompat#putOverridesOnReleaseBuilds}.
+ *
+ * <p>In addition, if {@code removeOtherOwnedOverrides} is true, removes any override that
+ * wasn't just added, whose change ID is in {@code ownedChangeIds} but not in {@code
+ * changeIdsToSkip}, via {@link IPlatformCompat#removeOverridesOnReleaseBuilds}.
+ */
+ private void applyPackageOverrides(String configStr, String packageName, long versionCode,
+ Set<Long> ownedChangeIds, Set<Long> changeIdsToSkip,
+ boolean removeOtherOwnedOverrides) {
+ Map<Long, PackageOverride> overridesToAdd = AppCompatOverridesParser.parsePackageOverrides(
+ configStr, versionCode, changeIdsToSkip);
+ putPackageOverrides(packageName, overridesToAdd);
+
+ if (!removeOtherOwnedOverrides) {
+ return;
+ }
+ Set<Long> overridesToRemove = new ArraySet<>();
+ for (Long changeId : ownedChangeIds) {
+ if (!overridesToAdd.containsKey(changeId) && !changeIdsToSkip.contains(changeId)) {
+ overridesToRemove.add(changeId);
+ }
+ }
+ removePackageOverrides(packageName, overridesToRemove);
+ }
+
+ /**
+ * Removes all owned overrides in all supported namespaces for the given {@code packageName}.
+ *
+ * <p>If a certain namespace doesn't have a package override flag for the given {@code
+ * packageName}, that namespace is skipped.</p>
+ */
+ private void removeAllPackageOverrides(String packageName) {
+ for (String namespace : mSupportedNamespaces) {
+ if (DeviceConfig.getString(namespace, packageName, /* defaultValue= */ "").isEmpty()) {
+ // No overrides for this package in this namespace.
+ continue;
+ }
+ // We remove overrides for each namespace separately so that if there is a failure for
+ // one namespace, the other namespaces won't be affected.
+ removePackageOverrides(packageName, getOwnedChangeIds(namespace));
+ }
+ }
+
+ /**
+ * Calls {@link IPlatformCompat#removeOverridesOnReleaseBuilds} on each package name and
+ * respective change IDs in {@code overridesToRemove}.
+ */
+ private void removeOverrides(Map<String, Set<Long>> overridesToRemove) {
+ for (Map.Entry<String, Set<Long>> packageNameAndOverrides : overridesToRemove.entrySet()) {
+ removePackageOverrides(packageNameAndOverrides.getKey(),
+ packageNameAndOverrides.getValue());
+ }
+ }
+
+ /**
+ * Fetches the value of {@link AppCompatOverridesParser#FLAG_REMOVE_OVERRIDES} for the given
+ * {@code namespace} and parses it into a map from package name to a set of change IDs to
+ * remove for that package.
+ */
+ private Map<String, Set<Long>> getOverridesToRemove(String namespace,
+ Set<Long> ownedChangeIds) {
+ return mOverridesParser.parseRemoveOverrides(
+ DeviceConfig.getString(namespace, FLAG_REMOVE_OVERRIDES, /* defaultValue= */ ""),
+ ownedChangeIds);
+ }
+
+ /**
+ * Fetches the value of {@link AppCompatOverridesParser#FLAG_OWNED_CHANGE_IDS} for the given
+ * {@code namespace} and parses it into a set of change IDs.
+ */
+ private static Set<Long> getOwnedChangeIds(String namespace) {
+ return AppCompatOverridesParser.parseOwnedChangeIds(
+ DeviceConfig.getString(namespace, FLAG_OWNED_CHANGE_IDS, /* defaultValue= */ ""));
+ }
+
+ private void putPackageOverrides(String packageName,
+ Map<Long, PackageOverride> overridesToAdd) {
+ if (overridesToAdd.isEmpty()) {
+ return;
+ }
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overridesToAdd);
+ try {
+ mPlatformCompat.putOverridesOnReleaseBuilds(config, packageName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call IPlatformCompat#putOverridesOnReleaseBuilds", e);
+ }
+ }
+
+ private void removePackageOverrides(String packageName, Set<Long> overridesToRemove) {
+ if (overridesToRemove.isEmpty()) {
+ return;
+ }
+ CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
+ overridesToRemove);
+ try {
+ mPlatformCompat.removeOverridesOnReleaseBuilds(config, packageName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call IPlatformCompat#removeOverridesOnReleaseBuilds", e);
+ }
+ }
+
+ private boolean isInstalledForAnyUser(String packageName) {
+ return getVersionCodeOrNull(packageName) != null;
+ }
+
+ @Nullable
+ private Long getVersionCodeOrNull(String packageName) {
+ try {
+ ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName,
+ MATCH_ANY_USER);
+ return applicationInfo.longVersionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package isn't installed for any user.
+ return null;
+ }
+ }
+
+ /**
+ * SystemService lifecycle for AppCompatOverridesService.
+ *
+ * @hide
+ */
+ public static final class Lifecycle extends SystemService {
+ private AppCompatOverridesService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new AppCompatOverridesService(getContext());
+ mService.registerDeviceConfigListeners();
+ mService.registerPackageReceiver();
+ }
+ }
+
+ /**
+ * A {@link DeviceConfig.OnPropertiesChangedListener} that listens on changes to a given
+ * namespace and adds/removes overrides according to the changed flags.
+ */
+ private final class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+ private final Context mContext;
+ private final String mNamespace;
+
+ private DeviceConfigListener(Context context, String namespace) {
+ mContext = context;
+ mNamespace = namespace;
+ }
+
+ private void register() {
+ DeviceConfig.addOnPropertiesChangedListener(mNamespace, mContext.getMainExecutor(),
+ this);
+ }
+
+ private void unregister() {
+ DeviceConfig.removeOnPropertiesChangedListener(this);
+ }
+
+ @Override
+ public void onPropertiesChanged(Properties properties) {
+ boolean removeOverridesFlagChanged = properties.getKeyset().contains(
+ FLAG_REMOVE_OVERRIDES);
+ boolean ownedChangedIdsFlagChanged = properties.getKeyset().contains(
+ FLAG_OWNED_CHANGE_IDS);
+
+ Set<Long> ownedChangeIds = getOwnedChangeIds(mNamespace);
+ Map<String, Set<Long>> overridesToRemove = getOverridesToRemove(mNamespace,
+ ownedChangeIds);
+ if (removeOverridesFlagChanged || ownedChangedIdsFlagChanged) {
+ // In both cases it's possible that overrides that weren't removed before should
+ // now be removed.
+ removeOverrides(overridesToRemove);
+ }
+
+ if (removeOverridesFlagChanged) {
+ // We need to re-apply all overrides in the namespace since the remove overrides
+ // flag might have blocked some of them from being applied before.
+ applyAllOverrides(mNamespace, ownedChangeIds, overridesToRemove);
+ } else {
+ applyOverrides(properties, ownedChangeIds, overridesToRemove);
+ }
+ }
+ }
+
+ /**
+ * A {@link BroadcastReceiver} that listens on package added/changed/removed events and
+ * adds/removes overrides according to the corresponding Device Config flags.
+ */
+ private final class PackageReceiver extends BroadcastReceiver {
+ private final Context mContext;
+ private final IntentFilter mIntentFilter;
+
+ private PackageReceiver(Context context) {
+ mContext = context;
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(ACTION_PACKAGE_ADDED);
+ mIntentFilter.addAction(ACTION_PACKAGE_CHANGED);
+ mIntentFilter.addAction(ACTION_PACKAGE_REMOVED);
+ mIntentFilter.addDataScheme("package");
+ }
+
+ private void register() {
+ mContext.registerReceiverForAllUsers(this, mIntentFilter, /* broadcastPermission= */
+ null, /* scheduler= */ null);
+ }
+
+ private void unregister() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
+ Uri data = intent.getData();
+ if (data == null) {
+ Slog.w(TAG, "Failed to get package name in package receiver");
+ return;
+ }
+ String packageName = data.getSchemeSpecificPart();
+ String action = intent.getAction();
+ if (action == null) {
+ Slog.w(TAG, "Failed to get action in package receiver");
+ return;
+ }
+ switch (action) {
+ case ACTION_PACKAGE_ADDED:
+ case ACTION_PACKAGE_CHANGED:
+ addAllPackageOverrides(packageName);
+ break;
+ case ACTION_PACKAGE_REMOVED:
+ if (!isInstalledForAnyUser(packageName)) {
+ removeAllPackageOverrides(packageName);
+ }
+ break;
+ default:
+ Slog.w(TAG, "Unsupported action in package receiver: " + action);
+ break;
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/compat/overrides/OWNERS b/services/core/java/com/android/server/compat/overrides/OWNERS
new file mode 100644
index 000000000000..b80f3402c19d
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/OWNERS
@@ -0,0 +1,2 @@
+tomnatan@google.com
+mariiasand@google.com
diff --git a/services/core/java/com/android/server/compat/overrides/TEST_MAPPING b/services/core/java/com/android/server/compat/overrides/TEST_MAPPING
new file mode 100644
index 000000000000..4b8f08ec9164
--- /dev/null
+++ b/services/core/java/com/android/server/compat/overrides/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.compat.overrides"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 091e6c4adf4d..a56a8ea993f0 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -227,7 +227,7 @@ public class MultipathPolicyTracker {
subscriberId = tele.getSubscriberId();
mNetworkTemplate = new NetworkTemplate(
NetworkTemplate.MATCH_MOBILE, subscriberId, new String[] { subscriberId },
- null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+ null, NetworkStats.METERED_YES, NetworkStats.ROAMING_ALL,
NetworkStats.DEFAULT_NETWORK_NO, NETWORK_TYPE_ALL, OEM_MANAGED_ALL,
SUBSCRIBER_ID_MATCH_RULE_EXACT);
mUsageCallback = new UsageCallback() {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1acbde91b353..3762ccaae13b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,6 +17,8 @@
package com.android.server.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.Manifest.permission.CONTROL_VPN;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
@@ -891,6 +893,7 @@ public class Vpn {
* - oldPackage null, newPackage non-null: ConfirmDialog calling prepareVpn().
* - oldPackage null, newPackage=LEGACY_VPN: Used internally to disconnect
* and revoke any current app VPN and re-prepare legacy vpn.
+ * - oldPackage null, newPackage null: always returns true for backward compatibility.
*
* TODO: Rename the variables - or split this method into two - and end this confusion.
* TODO: b/29032008 Migrate code from prepare(oldPackage=non-null, newPackage=LEGACY_VPN)
@@ -904,6 +907,18 @@ public class Vpn {
*/
public synchronized boolean prepare(
String oldPackage, String newPackage, @VpnManager.VpnType int vpnType) {
+ // Except for Settings and VpnDialogs, the caller should be matched one of oldPackage or
+ // newPackage. Otherwise, non VPN owner might get the VPN always-on status of the VPN owner.
+ // See b/191382886.
+ if (mContext.checkCallingOrSelfPermission(CONTROL_VPN) != PERMISSION_GRANTED) {
+ if (oldPackage != null) {
+ verifyCallingUidAndPackage(oldPackage);
+ }
+ if (newPackage != null) {
+ verifyCallingUidAndPackage(newPackage);
+ }
+ }
+
if (oldPackage != null) {
// Stop an existing always-on VPN from being dethroned by other apps.
if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) {
@@ -1803,14 +1818,13 @@ public class Vpn {
}
private void enforceControlPermission() {
- mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller");
+ mContext.enforceCallingPermission(CONTROL_VPN, "Unauthorized Caller");
}
private void enforceControlPermissionOrInternalCaller() {
// Require the caller to be either an application with CONTROL_VPN permission or a process
// in the system server.
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN,
- "Unauthorized Caller");
+ mContext.enforceCallingOrSelfPermission(CONTROL_VPN, "Unauthorized Caller");
}
private void enforceSettingsPermission() {
@@ -3115,8 +3129,9 @@ public class Vpn {
}
private void verifyCallingUidAndPackage(String packageName) {
- if (getAppUid(packageName, mUserId) != Binder.getCallingUid()) {
- throw new SecurityException("Mismatched package and UID");
+ final int callingUid = Binder.getCallingUid();
+ if (getAppUid(packageName, mUserId) != callingUid) {
+ throw new SecurityException(packageName + " does not belong to uid " + callingUid);
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index e693bcc93f8f..7fe24ff1f069 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -19,11 +19,14 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -39,6 +42,19 @@ import java.util.Objects;
* @see DeviceStateManagerService
*/
public final class DeviceState {
+ /**
+ * Flag that indicates sticky requests should be cancelled when this device state becomes the
+ * base device state.
+ */
+ public static final int FLAG_CANCEL_STICKY_REQUESTS = 1 << 0;
+
+ /** @hide */
+ @IntDef(prefix = {"FLAG_"}, flag = true, value = {
+ FLAG_CANCEL_STICKY_REQUESTS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceStateFlags {}
+
/** Unique identifier for the device state. */
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE)
private final int mIdentifier;
@@ -47,14 +63,19 @@ public final class DeviceState {
@NonNull
private final String mName;
+ @DeviceStateFlags
+ private final int mFlags;
+
public DeviceState(
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int identifier,
- @NonNull String name) {
+ @NonNull String name,
+ @DeviceStateFlags int flags) {
Preconditions.checkArgumentInRange(identifier, MINIMUM_DEVICE_STATE, MAXIMUM_DEVICE_STATE,
"identifier");
mIdentifier = identifier;
mName = name;
+ mFlags = flags;
}
/** Returns the unique identifier for the device state. */
@@ -69,6 +90,11 @@ public final class DeviceState {
return mName;
}
+ @DeviceStateFlags
+ public int getFlags() {
+ return mFlags;
+ }
+
@Override
public String toString() {
return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' + '}';
@@ -80,11 +106,12 @@ public final class DeviceState {
if (o == null || getClass() != o.getClass()) return false;
DeviceState that = (DeviceState) o;
return mIdentifier == that.mIdentifier
- && Objects.equals(mName, that.mName);
+ && Objects.equals(mName, that.mName)
+ && mFlags == that.mFlags;
}
@Override
public int hashCode() {
- return Objects.hash(mIdentifier, mName);
+ return Objects.hash(mIdentifier, mName, mFlags);
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index a8b0994402e8..806a5dd65a13 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,8 +19,12 @@ package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.hardware.devicestate.DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
+
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,12 +34,11 @@ import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.IDeviceStateManager;
import android.hardware.devicestate.IDeviceStateManagerCallback;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -43,14 +46,21 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.DisplayThread;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.policy.DeviceStatePolicyImpl;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowProcessController;
import java.io.FileDescriptor;
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.Optional;
+import java.util.WeakHashMap;
/**
* A system service that manages the state of a device with user-configurable hardware like a
@@ -81,10 +91,19 @@ public final class DeviceStateManagerService extends SystemService {
private static final boolean DEBUG = false;
private final Object mLock = new Object();
+ // Handler on the {@link DisplayThread} used to dispatch calls to the policy and to registered
+ // callbacks though its handler (mHandler). Provides a guarantee of callback order when
+ // leveraging mHandler and also enables posting messages with the service lock held.
+ private final Handler mHandler;
@NonNull
private final DeviceStatePolicy mDeviceStatePolicy;
@NonNull
private final BinderService mBinderService;
+ @NonNull
+ private final OverrideRequestController mOverrideRequestController;
+ @VisibleForTesting
+ @NonNull
+ public ActivityTaskManagerInternal mActivityTaskManagerInternal;
// All supported device states keyed by identifier.
@GuardedBy("mLock")
@@ -109,17 +128,16 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull
private Optional<DeviceState> mBaseState = Optional.empty();
+ // The current active override request. When set the device state specified here will take
+ // precedence over mBaseState.
+ @GuardedBy("mLock")
+ @NonNull
+ private Optional<OverrideRequest> mActiveOverride = Optional.empty();
+
// List of processes registered to receive notifications about changes to device state and
// request status indexed by process id.
@GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
- // List of override requests with the highest precedence request at the end.
- @GuardedBy("mLock")
- private final ArrayList<OverrideRequestRecord> mRequestRecords = new ArrayList<>();
- // Set of override requests that are pending a call to notifyStatusIfNeeded() to be notified
- // of a change in status.
- @GuardedBy("mLock")
- private final ArraySet<OverrideRequestRecord> mRequestsPendingStatusChange = new ArraySet<>();
public DeviceStateManagerService(@NonNull Context context) {
this(context, new DeviceStatePolicyImpl(context));
@@ -128,9 +146,16 @@ public final class DeviceStateManagerService extends SystemService {
@VisibleForTesting
DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
super(context);
+ // We use the DisplayThread because this service indirectly drives
+ // display (on/off) and window (position) events through its callbacks.
+ DisplayThread displayThread = DisplayThread.get();
+ mHandler = new Handler(displayThread.getLooper());
+ mOverrideRequestController = new OverrideRequestController(
+ this::onOverrideRequestStatusChangedLocked);
mDeviceStatePolicy = policy;
mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
mBinderService = new BinderService();
+ mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
}
@Override
@@ -138,6 +163,11 @@ public final class DeviceStateManagerService extends SystemService {
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
}
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
+
/**
* Returns the current state the system is in. Note that the system may be in the process of
* configuring a different state.
@@ -191,12 +221,10 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull
Optional<DeviceState> getOverrideState() {
synchronized (mLock) {
- if (mRequestRecords.isEmpty()) {
- return Optional.empty();
+ if (mActiveOverride.isPresent()) {
+ return getStateLocked(mActiveOverride.get().getRequestedState());
}
-
- OverrideRequestRecord topRequest = mRequestRecords.get(mRequestRecords.size() - 1);
- return Optional.of(topRequest.mRequestedState);
+ return Optional.empty();
}
}
@@ -247,43 +275,41 @@ public final class DeviceStateManagerService extends SystemService {
}
private void updateSupportedStates(DeviceState[] supportedDeviceStates) {
- boolean updatedPendingState;
- boolean hasBaseState;
synchronized (mLock) {
final int[] oldStateIdentifiers = getSupportedStateIdentifiersLocked();
+ // Whether or not at least one device state has the flag FLAG_CANCEL_STICKY_REQUESTS
+ // set. If set to true, the OverrideRequestController will be configured to allow sticky
+ // requests.
+ boolean hasTerminalDeviceState = false;
mDeviceStates.clear();
for (int i = 0; i < supportedDeviceStates.length; i++) {
DeviceState state = supportedDeviceStates[i];
+ if ((state.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
+ hasTerminalDeviceState = true;
+ }
mDeviceStates.put(state.getIdentifier(), state);
}
+ mOverrideRequestController.setStickyRequestsAllowed(hasTerminalDeviceState);
+
final int[] newStateIdentifiers = getSupportedStateIdentifiersLocked();
if (Arrays.equals(oldStateIdentifiers, newStateIdentifiers)) {
return;
}
- final int requestSize = mRequestRecords.size();
- for (int i = 0; i < requestSize; i++) {
- OverrideRequestRecord request = mRequestRecords.get(i);
- if (!isSupportedStateLocked(request.mRequestedState.getIdentifier())) {
- request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
- }
- }
+ mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
+ updatePendingStateLocked();
- updatedPendingState = updatePendingStateLocked();
- hasBaseState = mBaseState.isPresent();
- }
+ if (!mPendingState.isPresent()) {
+ // If the change in the supported states didn't result in a change of the pending
+ // state commitPendingState() will never be called and the callbacks will never be
+ // notified of the change.
+ notifyDeviceStateInfoChangedAsync();
+ }
- if (hasBaseState && !updatedPendingState) {
- // If the change in the supported states didn't result in a change of the pending state
- // commitPendingState() will never be called and the callbacks will never be notified
- // of the change.
- notifyDeviceStateInfoChanged();
+ mHandler.post(this::notifyPolicyIfNeeded);
}
-
- notifyRequestsOfStatusChangeIfNeeded();
- notifyPolicyIfNeeded();
}
/**
@@ -311,7 +337,6 @@ public final class DeviceStateManagerService extends SystemService {
* @see #isSupportedStateLocked(int)
*/
private void setBaseState(int identifier) {
- boolean updatedPendingState;
synchronized (mLock) {
final Optional<DeviceState> baseStateOptional = getStateLocked(identifier);
if (!baseStateOptional.isPresent()) {
@@ -325,26 +350,21 @@ public final class DeviceStateManagerService extends SystemService {
}
mBaseState = Optional.of(baseState);
- final int requestSize = mRequestRecords.size();
- for (int i = 0; i < requestSize; i++) {
- OverrideRequestRecord request = mRequestRecords.get(i);
- if ((request.mFlags & FLAG_CANCEL_WHEN_BASE_CHANGES) > 0) {
- request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
- }
+ if ((baseState.getFlags() & DeviceState.FLAG_CANCEL_STICKY_REQUESTS) != 0) {
+ mOverrideRequestController.cancelStickyRequests();
}
+ mOverrideRequestController.handleBaseStateChanged();
+ updatePendingStateLocked();
- updatedPendingState = updatePendingStateLocked();
- }
+ if (!mPendingState.isPresent()) {
+ // If the change in base state didn't result in a change of the pending state
+ // commitPendingState() will never be called and the callbacks will never be
+ // notified of the change.
+ notifyDeviceStateInfoChangedAsync();
+ }
- if (!updatedPendingState) {
- // If the change in base state didn't result in a change of the pending state
- // commitPendingState() will never be called and the callbacks will never be notified
- // of the change.
- notifyDeviceStateInfoChanged();
+ mHandler.post(this::notifyPolicyIfNeeded);
}
-
- notifyRequestsOfStatusChangeIfNeeded();
- notifyPolicyIfNeeded();
}
/**
@@ -362,8 +382,8 @@ public final class DeviceStateManagerService extends SystemService {
}
final DeviceState stateToConfigure;
- if (!mRequestRecords.isEmpty()) {
- stateToConfigure = mRequestRecords.get(mRequestRecords.size() - 1).mRequestedState;
+ if (mActiveOverride.isPresent()) {
+ stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get();
} else if (mBaseState.isPresent()
&& isSupportedStateLocked(mBaseState.get().getIdentifier())) {
// Base state could have recently become unsupported after a change in supported states.
@@ -429,108 +449,106 @@ public final class DeviceStateManagerService extends SystemService {
* </p>
*/
private void commitPendingState() {
- // Update the current state.
synchronized (mLock) {
final DeviceState newState = mPendingState.get();
if (DEBUG) {
Slog.d(TAG, "Committing state: " + newState);
}
- if (!mRequestRecords.isEmpty()) {
- final OverrideRequestRecord topRequest =
- mRequestRecords.get(mRequestRecords.size() - 1);
- if (topRequest.mRequestedState.getIdentifier() == newState.getIdentifier()) {
- // The top request could have come in while the service was awaiting callback
- // from the policy. In that case we only set it to active if it matches the
- // current committed state, otherwise it will be set to active when its
- // requested state is committed.
- topRequest.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE);
- }
- }
-
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED,
newState.getIdentifier(), !mCommittedState.isPresent());
mCommittedState = Optional.of(newState);
mPendingState = Optional.empty();
updatePendingStateLocked();
- }
-
- // Notify callbacks of a change.
- notifyDeviceStateInfoChanged();
- // Notify the top request that it's active.
- notifyRequestsOfStatusChangeIfNeeded();
-
- // Try to configure the next state if needed.
- notifyPolicyIfNeeded();
- }
+ // Notify callbacks of a change.
+ notifyDeviceStateInfoChangedAsync();
+
+ // The top request could have come in while the service was awaiting callback
+ // from the policy. In that case we only set it to active if it matches the
+ // current committed state, otherwise it will be set to active when its
+ // requested state is committed.
+ OverrideRequest activeRequest = mActiveOverride.orElse(null);
+ if (activeRequest != null
+ && activeRequest.getRequestedState() == newState.getIdentifier()) {
+ ProcessRecord processRecord = mProcessRecords.get(activeRequest.getPid());
+ if (processRecord != null) {
+ processRecord.notifyRequestActiveAsync(activeRequest.getToken());
+ }
+ }
- private void notifyDeviceStateInfoChanged() {
- if (Thread.holdsLock(mLock)) {
- throw new IllegalStateException(
- "Attempting to notify callbacks with service lock held.");
+ // Try to configure the next state if needed.
+ mHandler.post(this::notifyPolicyIfNeeded);
}
+ }
- // Grab the lock and copy the process records and the current info.
- ArrayList<ProcessRecord> registeredProcesses;
- DeviceStateInfo info;
+ private void notifyDeviceStateInfoChangedAsync() {
synchronized (mLock) {
if (mProcessRecords.size() == 0) {
return;
}
- registeredProcesses = new ArrayList<>();
+ ArrayList<ProcessRecord> registeredProcesses = new ArrayList<>();
for (int i = 0; i < mProcessRecords.size(); i++) {
registeredProcesses.add(mProcessRecords.valueAt(i));
}
- info = getDeviceStateInfoLocked();
- }
+ DeviceStateInfo info = getDeviceStateInfoLocked();
- // After releasing the lock, send the notifications out.
- for (int i = 0; i < registeredProcesses.size(); i++) {
- registeredProcesses.get(i).notifyDeviceStateInfoAsync(info);
+ for (int i = 0; i < registeredProcesses.size(); i++) {
+ registeredProcesses.get(i).notifyDeviceStateInfoAsync(info);
+ }
}
}
- /**
- * Notifies all dirty requests (requests that have a change in status, but have not yet been
- * notified) that their status has changed.
- */
- private void notifyRequestsOfStatusChangeIfNeeded() {
- if (Thread.holdsLock(mLock)) {
- throw new IllegalStateException(
- "Attempting to notify requests with service lock held.");
+ private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
+ @OverrideRequestController.RequestStatus int status) {
+ if (status == STATUS_ACTIVE) {
+ mActiveOverride = Optional.of(request);
+ } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) {
+ if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
+ mActiveOverride = Optional.empty();
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown request status: " + status);
}
- ArraySet<OverrideRequestRecord> dirtyRequests;
- synchronized (mLock) {
- if (mRequestsPendingStatusChange.isEmpty()) {
- return;
- }
+ boolean updatedPendingState = updatePendingStateLocked();
- dirtyRequests = new ArraySet<>(mRequestsPendingStatusChange);
- mRequestsPendingStatusChange.clear();
+ ProcessRecord processRecord = mProcessRecords.get(request.getPid());
+ if (processRecord == null) {
+ // If the process is no longer registered with the service, for example if it has died,
+ // there is no need to notify it of a change in request status.
+ mHandler.post(this::notifyPolicyIfNeeded);
+ return;
}
- // After releasing the lock, send the notifications out.
- for (int i = 0; i < dirtyRequests.size(); i++) {
- dirtyRequests.valueAt(i).notifyStatusIfNeeded();
+ if (status == STATUS_ACTIVE) {
+ if (!updatedPendingState && !mPendingState.isPresent()) {
+ // If the pending state was not updated and there is not currently a pending state
+ // then this newly active request will never be notified of a change in state.
+ // Schedule the notification now.
+ processRecord.notifyRequestActiveAsync(request.getToken());
+ }
+ } else if (status == STATUS_SUSPENDED) {
+ processRecord.notifyRequestSuspendedAsync(request.getToken());
+ } else {
+ processRecord.notifyRequestCanceledAsync(request.getToken());
}
+
+ mHandler.post(this::notifyPolicyIfNeeded);
}
private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
- DeviceStateInfo currentInfo;
- ProcessRecord record;
- // Grab the lock to register the callback and get the current state.
synchronized (mLock) {
if (mProcessRecords.contains(pid)) {
throw new SecurityException("The calling process has already registered an"
+ " IDeviceStateManagerCallback.");
}
- record = new ProcessRecord(callback, pid);
+ ProcessRecord record = new ProcessRecord(callback, pid, this::handleProcessDied,
+ mHandler);
try {
callback.asBinder().linkToDeath(record, 0);
} catch (RemoteException ex) {
@@ -538,34 +556,21 @@ public final class DeviceStateManagerService extends SystemService {
}
mProcessRecords.put(pid, record);
- 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);
+ 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);
+ }
}
}
private void handleProcessDied(ProcessRecord processRecord) {
synchronized (mLock) {
- // Cancel all requests from this process.
- final int requestCount = processRecord.mRequestRecords.size();
- for (int i = 0; i < requestCount; i++) {
- final OverrideRequestRecord request = processRecord.mRequestRecords.valueAt(i);
- // Cancel the request but don't mark it as dirty since there's no need to send
- // notifications if the process has died.
- request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED,
- false /* markDirty */);
- }
-
mProcessRecords.remove(processRecord.mPid);
-
- updatePendingStateLocked();
+ mOverrideRequestController.handleProcessDied(processRecord.mPid);
}
-
- notifyPolicyIfNeeded();
}
private void requestStateInternal(int state, int flags, int callingPid,
@@ -577,7 +582,7 @@ public final class DeviceStateManagerService extends SystemService {
+ " has no registered callback.");
}
- if (processRecord.mRequestRecords.get(token) != null) {
+ if (mOverrideRequestController.hasRequest(token)) {
throw new IllegalStateException("Request has already been made for the supplied"
+ " token: " + token);
}
@@ -588,27 +593,9 @@ public final class DeviceStateManagerService extends SystemService {
+ " is not supported.");
}
- OverrideRequestRecord topRecord = mRequestRecords.isEmpty()
- ? null : mRequestRecords.get(mRequestRecords.size() - 1);
- if (topRecord != null) {
- topRecord.setStatusLocked(OverrideRequestRecord.STATUS_SUSPENDED);
- }
-
- final OverrideRequestRecord request =
- new OverrideRequestRecord(processRecord, token, deviceState.get(), flags);
- mRequestRecords.add(request);
- processRecord.mRequestRecords.put(request.mToken, request);
-
- final boolean updatedPendingState = updatePendingStateLocked();
- if (!updatedPendingState && !mPendingState.isPresent()) {
- // We don't set the status of the new request to ACTIVE if the request updated the
- // pending state as it will be set in commitPendingState().
- request.setStatusLocked(OverrideRequestRecord.STATUS_ACTIVE, true /* markDirty */);
- }
+ OverrideRequest request = new OverrideRequest(token, callingPid, state, flags);
+ mOverrideRequestController.addRequest(request);
}
-
- notifyRequestsOfStatusChangeIfNeeded();
- notifyPolicyIfNeeded();
}
private void cancelRequestInternal(int callingPid, @NonNull IBinder token) {
@@ -619,18 +606,8 @@ public final class DeviceStateManagerService extends SystemService {
+ " has no registered callback.");
}
- OverrideRequestRecord request = processRecord.mRequestRecords.get(token);
- if (request == null) {
- throw new IllegalStateException("No known request for the given token");
- }
-
- request.setStatusLocked(OverrideRequestRecord.STATUS_CANCELED);
-
- updatePendingStateLocked();
+ mOverrideRequestController.cancelRequest(token);
}
-
- notifyRequestsOfStatusChangeIfNeeded();
- notifyPolicyIfNeeded();
}
private void dumpInternal(PrintWriter pw) {
@@ -650,16 +627,7 @@ public final class DeviceStateManagerService extends SystemService {
pw.println(" " + i + ": mPid=" + processRecord.mPid);
}
- final int requestCount = mRequestRecords.size();
- pw.println();
- pw.println("Override requests: size=" + requestCount);
- for (int i = 0; i < requestCount; i++) {
- OverrideRequestRecord requestRecord = mRequestRecords.get(i);
- pw.println(" " + i + ": mPid=" + requestRecord.mProcessRecord.mPid
- + ", mRequestedState=" + requestRecord.mRequestedState
- + ", mFlags=" + requestRecord.mFlags
- + ", mStatus=" + requestRecord.statusToString(requestRecord.mStatus));
- }
+ mOverrideRequestController.dumpInternal(pw);
}
}
@@ -683,142 +651,107 @@ public final class DeviceStateManagerService extends SystemService {
}
}
- private final class ProcessRecord implements IBinder.DeathRecipient {
+ private static final class ProcessRecord implements IBinder.DeathRecipient {
+ public interface DeathListener {
+ void onProcessDied(ProcessRecord record);
+ }
+
+ private static final int STATUS_ACTIVE = 0;
+
+ private static final int STATUS_SUSPENDED = 1;
+
+ private static final int STATUS_CANCELED = 2;
+
+ @IntDef(prefix = {"STATUS_"}, value = {
+ STATUS_ACTIVE,
+ STATUS_SUSPENDED,
+ STATUS_CANCELED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface RequestStatus {}
+
private final IDeviceStateManagerCallback mCallback;
private final int mPid;
+ private final DeathListener mDeathListener;
+ private final Handler mHandler;
- private final ArrayMap<IBinder, OverrideRequestRecord> mRequestRecords = new ArrayMap<>();
+ private final WeakHashMap<IBinder, Integer> mLastNotifiedStatus = new WeakHashMap<>();
- ProcessRecord(IDeviceStateManagerCallback callback, int pid) {
+ ProcessRecord(IDeviceStateManagerCallback callback, int pid, DeathListener deathListener,
+ Handler handler) {
mCallback = callback;
mPid = pid;
+ mDeathListener = deathListener;
+ mHandler = handler;
}
@Override
public void binderDied() {
- handleProcessDied(this);
+ mDeathListener.onProcessDied(this);
}
public void notifyDeviceStateInfoAsync(@NonNull DeviceStateInfo info) {
- try {
- mCallback.onDeviceStateInfoChanged(info);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.",
- ex);
- }
+ mHandler.post(() -> {
+ try {
+ mCallback.onDeviceStateInfoChanged(info);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process " + mPid + " that device state changed.",
+ ex);
+ }
+ });
}
- public void notifyRequestActiveAsync(OverrideRequestRecord request) {
- try {
- mCallback.onRequestActive(request.mToken);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
- ex);
+ public void notifyRequestActiveAsync(IBinder token) {
+ @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
+ if (lastStatus != null
+ && (lastStatus == STATUS_ACTIVE || lastStatus == STATUS_CANCELED)) {
+ return;
}
- }
- public void notifyRequestSuspendedAsync(OverrideRequestRecord request) {
- try {
- mCallback.onRequestSuspended(request.mToken);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
- ex);
- }
+ mLastNotifiedStatus.put(token, STATUS_ACTIVE);
+ mHandler.post(() -> {
+ try {
+ mCallback.onRequestActive(token);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
+ ex);
+ }
+ });
}
- public void notifyRequestCanceledAsync(OverrideRequestRecord request) {
- try {
- mCallback.onRequestCanceled(request.mToken);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
- ex);
+ public void notifyRequestSuspendedAsync(IBinder token) {
+ @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
+ if (lastStatus != null
+ && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) {
+ return;
}
- }
- }
-
- /** A record describing a request to override the state of the device. */
- private final class OverrideRequestRecord {
- public static final int STATUS_UNKNOWN = 0;
- public static final int STATUS_ACTIVE = 1;
- public static final int STATUS_SUSPENDED = 2;
- public static final int STATUS_CANCELED = 3;
-
- @Nullable
- public String statusToString(int status) {
- switch (status) {
- case STATUS_ACTIVE:
- return "ACTIVE";
- case STATUS_SUSPENDED:
- return "SUSPENDED";
- case STATUS_CANCELED:
- return "CANCELED";
- case STATUS_UNKNOWN:
- return "UNKNOWN";
- default:
- return null;
- }
- }
-
- private final ProcessRecord mProcessRecord;
- @NonNull
- private final IBinder mToken;
- @NonNull
- private final DeviceState mRequestedState;
- private final int mFlags;
-
- private int mStatus = STATUS_UNKNOWN;
- private int mLastNotifiedStatus = STATUS_UNKNOWN;
-
- OverrideRequestRecord(@NonNull ProcessRecord processRecord, @NonNull IBinder token,
- @NonNull DeviceState requestedState, int flags) {
- mProcessRecord = processRecord;
- mToken = token;
- mRequestedState = requestedState;
- mFlags = flags;
- }
-
- public void setStatusLocked(int status) {
- setStatusLocked(status, true /* markDirty */);
- }
-
- public void setStatusLocked(int status, boolean markDirty) {
- if (mStatus != status) {
- if (mStatus == STATUS_CANCELED) {
- throw new IllegalStateException(
- "Can not alter the status of a request after set to CANCELED.");
- }
-
- mStatus = status;
-
- if (mStatus == STATUS_CANCELED) {
- mRequestRecords.remove(this);
- mProcessRecord.mRequestRecords.remove(mToken);
- }
- if (markDirty) {
- mRequestsPendingStatusChange.add(this);
+ mLastNotifiedStatus.put(token, STATUS_SUSPENDED);
+ mHandler.post(() -> {
+ try {
+ mCallback.onRequestSuspended(token);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
+ ex);
}
- }
+ });
}
- public void notifyStatusIfNeeded() {
- int stateToReport;
- synchronized (mLock) {
- if (mLastNotifiedStatus == mStatus) {
- return;
- }
-
- stateToReport = mStatus;
- mLastNotifiedStatus = mStatus;
+ public void notifyRequestCanceledAsync(IBinder token) {
+ @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token);
+ if (lastStatus != null && lastStatus == STATUS_CANCELED) {
+ return;
}
- if (stateToReport == STATUS_ACTIVE) {
- mProcessRecord.notifyRequestActiveAsync(this);
- } else if (stateToReport == STATUS_SUSPENDED) {
- mProcessRecord.notifyRequestSuspendedAsync(this);
- } else if (stateToReport == STATUS_CANCELED) {
- mProcessRecord.notifyRequestCanceledAsync(this);
- }
+ mLastNotifiedStatus.put(token, STATUS_CANCELED);
+ mHandler.post(() -> {
+ try {
+ mCallback.onRequestCanceled(token);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.",
+ ex);
+ }
+ });
}
}
@@ -848,14 +781,21 @@ public final class DeviceStateManagerService extends SystemService {
@Override // Binder call
public void requestState(IBinder token, int state, int flags) {
- getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
- "Permission required to request device state.");
+ final int callingPid = Binder.getCallingPid();
+ // Allow top processes to request a device state change
+ // If the calling process ID is not the top app, then we check if this process
+ // holds a permission to CONTROL_DEVICE_STATE
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ if (topApp.getPid() != callingPid) {
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to request device state, "
+ + "or the call must come from the top focused app.");
+ }
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
}
- final int callingPid = Binder.getCallingPid();
final long callingIdentity = Binder.clearCallingIdentity();
try {
requestStateInternal(state, flags, callingPid, token);
@@ -866,14 +806,21 @@ public final class DeviceStateManagerService extends SystemService {
@Override // Binder call
public void cancelRequest(IBinder token) {
- getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
- "Permission required to clear requested device state.");
+ final int callingPid = Binder.getCallingPid();
+ // Allow top processes to cancel a device state change
+ // If the calling process ID is not the top app, then we check if this process
+ // holds a permission to CONTROL_DEVICE_STATE
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ if (topApp.getPid() != callingPid) {
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "Permission required to cancel device state, "
+ + "or the call must come from the top focused app.");
+ }
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
}
- final int callingPid = Binder.getCallingPid();
final long callingIdentity = Binder.clearCallingIdentity();
try {
cancelRequestInternal(callingPid, token);
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 56b68b73cb57..eed68f8b1300 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -27,7 +27,9 @@ import android.os.Binder;
import android.os.ShellCommand;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* ShellCommands for {@link DeviceStateManagerService}.
@@ -56,14 +58,18 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
switch (cmd) {
case "state":
return runState(pw);
+ case "print-state":
+ return runPrintState(pw);
case "print-states":
return runPrintStates(pw);
+ case "print-states-simple":
+ return runPrintStatesSimple(pw);
default:
return handleDefaultCommands(cmd);
}
}
- private void printState(PrintWriter pw) {
+ private void printAllStates(PrintWriter pw) {
Optional<DeviceState> committedState = mService.getCommittedState();
Optional<DeviceState> baseState = mService.getBaseState();
Optional<DeviceState> overrideState = mService.getOverrideState();
@@ -79,7 +85,8 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
private int runState(PrintWriter pw) {
final String nextArg = getNextArg();
if (nextArg == null) {
- printState(pw);
+ printAllStates(pw);
+ return 0;
}
final Context context = mService.getContext();
@@ -123,6 +130,16 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runPrintState(PrintWriter pw) {
+ Optional<DeviceState> deviceState = mService.getCommittedState();
+ if (deviceState.isPresent()) {
+ pw.println(deviceState.get().getIdentifier());
+ return 0;
+ }
+ getErrPrintWriter().println("Error: device state not available.");
+ return 1;
+ }
+
private int runPrintStates(PrintWriter pw) {
DeviceState[] states = mService.getSupportedStates();
pw.print("Supported states: [\n");
@@ -133,6 +150,14 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runPrintStatesSimple(PrintWriter pw) {
+ pw.print(Arrays.stream(mService.getSupportedStates())
+ .map(DeviceState::getIdentifier)
+ .map(Object::toString)
+ .collect(Collectors.joining(",")));
+ return 0;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -141,8 +166,12 @@ public class DeviceStateManagerShellCommand extends ShellCommand {
pw.println(" Print this help text.");
pw.println(" state [reset|OVERRIDE_DEVICE_STATE]");
pw.println(" Return or override device state.");
+ pw.println(" print-state");
+ pw.println(" Return the current device state.");
pw.println(" print-states");
pw.println(" Return list of currently supported device states.");
+ pw.println(" print-states-simple");
+ pw.println(" Return the currently supported device states in comma separated format.");
}
private static String toString(@NonNull Optional<DeviceState> state) {
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
new file mode 100644
index 000000000000..35a4c844c710
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.devicestate;
+
+import android.hardware.devicestate.DeviceStateRequest;
+import android.os.IBinder;
+
+/**
+ * A request to override the state managed by {@link DeviceStateManagerService}.
+ *
+ * @see OverrideRequestController
+ */
+final class OverrideRequest {
+ private final IBinder mToken;
+ private final int mPid;
+ private final int mRequestedState;
+ @DeviceStateRequest.RequestFlags
+ private final int mFlags;
+
+ OverrideRequest(IBinder token, int pid, int requestedState,
+ @DeviceStateRequest.RequestFlags int flags) {
+ mToken = token;
+ mPid = pid;
+ mRequestedState = requestedState;
+ mFlags = flags;
+ }
+
+ IBinder getToken() {
+ return mToken;
+ }
+
+ int getPid() {
+ return mPid;
+ }
+
+ int getRequestedState() {
+ return mRequestedState;
+ }
+
+ @DeviceStateRequest.RequestFlags
+ int getFlags() {
+ return mFlags;
+ }
+}
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
new file mode 100644
index 000000000000..05c9eb2c5bbe
--- /dev/null
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -0,0 +1,322 @@
+/*
+ * 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.devicestate;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.os.IBinder;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the lifecycle of override requests.
+ * <p>
+ * New requests are added with {@link #addRequest(OverrideRequest)} and are kept active until
+ * either:
+ * <ul>
+ * <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the
+ * request will become suspended.</li>
+ * <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect
+ * of other methods calls, such as {@link #handleProcessDied(int)}.</li>
+ * </ul>
+ */
+final class OverrideRequestController {
+ static final int STATUS_UNKNOWN = 0;
+ /**
+ * The request is the top-most request.
+ */
+ static final int STATUS_ACTIVE = 1;
+ /**
+ * The request is still present but is being superseded by another request.
+ */
+ static final int STATUS_SUSPENDED = 2;
+ /**
+ * The request is not longer valid.
+ */
+ static final int STATUS_CANCELED = 3;
+
+ @IntDef(prefix = {"STATUS_"}, value = {
+ STATUS_UNKNOWN,
+ STATUS_ACTIVE,
+ STATUS_SUSPENDED,
+ STATUS_CANCELED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RequestStatus {}
+
+ static String statusToString(@RequestStatus int status) {
+ switch (status) {
+ case STATUS_ACTIVE:
+ return "ACTIVE";
+ case STATUS_SUSPENDED:
+ return "SUSPENDED";
+ case STATUS_CANCELED:
+ return "CANCELED";
+ case STATUS_UNKNOWN:
+ return "UNKNOWN";
+ }
+ throw new IllegalArgumentException("Unknown status: " + status);
+ }
+
+ private final StatusChangeListener mListener;
+ private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>();
+
+ // List of override requests with the most recent override request at the end.
+ private final ArrayList<OverrideRequest> mRequests = new ArrayList<>();
+
+ private boolean mStickyRequestsAllowed;
+ // List of override requests that have outlived their process and will only be cancelled through
+ // a call to cancelStickyRequests().
+ private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>();
+
+ OverrideRequestController(@NonNull StatusChangeListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call
+ * to {@link #handleProcessDied(int)} will not result in the request being cancelled
+ * immediately. Instead, the request will be marked sticky and must be cancelled with a call
+ * to {@link #cancelStickyRequests()}.
+ */
+ void setStickyRequestsAllowed(boolean stickyRequestsAllowed) {
+ mStickyRequestsAllowed = stickyRequestsAllowed;
+ if (!mStickyRequestsAllowed) {
+ cancelStickyRequests();
+ }
+ }
+
+ /**
+ * Adds a request to the top of the stack and notifies the listener of all changes to request
+ * status as a result of this operation.
+ */
+ void addRequest(@NonNull OverrideRequest request) {
+ mRequests.add(request);
+ mListener.onStatusChanged(request, STATUS_ACTIVE);
+
+ if (mRequests.size() > 1) {
+ OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2);
+ mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED);
+ }
+ }
+
+ /**
+ * Cancels the request with the specified {@code token} and notifies the listener of all changes
+ * to request status as a result of this operation.
+ */
+ void cancelRequest(@NonNull IBinder token) {
+ int index = getRequestIndex(token);
+ if (index == -1) {
+ return;
+ }
+
+ OverrideRequest request = mRequests.remove(index);
+ if (index == mRequests.size() && mRequests.size() > 0) {
+ // We removed the current active request so we need to set the new active request
+ // before cancelling this request.
+ OverrideRequest newTop = getLast(mRequests);
+ mListener.onStatusChanged(newTop, STATUS_ACTIVE);
+ }
+ mListener.onStatusChanged(request, STATUS_CANCELED);
+ }
+
+ /**
+ * Cancels all requests that are currently marked sticky and notifies the listener of all
+ * changes to request status as a result of this operation.
+ *
+ * @see #setStickyRequestsAllowed(boolean)
+ */
+ void cancelStickyRequests() {
+ mTmpRequestsToCancel.clear();
+ mTmpRequestsToCancel.addAll(mStickyRequests);
+ cancelRequestsLocked(mTmpRequestsToCancel);
+ }
+
+ /**
+ * Returns {@code true} if this controller is current managing a request with the specified
+ * {@code token}, {@code false} otherwise.
+ */
+ boolean hasRequest(@NonNull IBinder token) {
+ return getRequestIndex(token) != -1;
+ }
+
+ /**
+ * Notifies the controller that the process with the specified {@code pid} has died. The
+ * controller will notify the listener of all changes to request status as a result of this
+ * operation.
+ */
+ void handleProcessDied(int pid) {
+ if (mRequests.isEmpty()) {
+ return;
+ }
+
+ mTmpRequestsToCancel.clear();
+ OverrideRequest prevActiveRequest = getLast(mRequests);
+ for (OverrideRequest request : mRequests) {
+ if (request.getPid() == pid) {
+ mTmpRequestsToCancel.add(request);
+ }
+ }
+
+ if (mStickyRequestsAllowed) {
+ // Do not cancel the requests now because sticky requests are allowed. These
+ // requests will be cancelled on a call to cancelStickyRequests().
+ mStickyRequests.addAll(mTmpRequestsToCancel);
+ return;
+ }
+
+ cancelRequestsLocked(mTmpRequestsToCancel);
+ }
+
+ /**
+ * Notifies the controller that the base state has changed. The controller will notify the
+ * listener of all changes to request status as a result of this change.
+ *
+ * @return {@code true} if calling this method has lead to a new active request, {@code false}
+ * otherwise.
+ */
+ boolean handleBaseStateChanged() {
+ if (mRequests.isEmpty()) {
+ return false;
+ }
+
+ mTmpRequestsToCancel.clear();
+ OverrideRequest prevActiveRequest = getLast(mRequests);
+ for (int i = 0; i < mRequests.size(); i++) {
+ OverrideRequest request = mRequests.get(i);
+ if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) {
+ mTmpRequestsToCancel.add(request);
+ }
+ }
+
+ final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
+ return newActiveRequest;
+ }
+
+ /**
+ * Notifies the controller that the set of supported states has changed. The controller will
+ * notify the listener of all changes to request status as a result of this change.
+ *
+ * @return {@code true} if calling this method has lead to a new active request, {@code false}
+ * otherwise.
+ */
+ boolean handleNewSupportedStates(int[] newSupportedStates) {
+ if (mRequests.isEmpty()) {
+ return false;
+ }
+
+ mTmpRequestsToCancel.clear();
+ for (int i = 0; i < mRequests.size(); i++) {
+ OverrideRequest request = mRequests.get(i);
+ if (!contains(newSupportedStates, request.getRequestedState())) {
+ mTmpRequestsToCancel.add(request);
+ }
+ }
+
+ final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel);
+ return newActiveRequest;
+ }
+
+ void dumpInternal(PrintWriter pw) {
+ final int requestCount = mRequests.size();
+ pw.println();
+ pw.println("Override requests: size=" + requestCount);
+ for (int i = 0; i < requestCount; i++) {
+ OverrideRequest overrideRequest = mRequests.get(i);
+ int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED;
+ pw.println(" " + i + ": mPid=" + overrideRequest.getPid()
+ + ", mRequestedState=" + overrideRequest.getRequestedState()
+ + ", mFlags=" + overrideRequest.getFlags()
+ + ", mStatus=" + statusToString(status));
+ }
+ }
+
+ /**
+ * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new
+ * request becoming active this request will also be notified of its change in state.
+ *
+ * @return {@code true} if calling this method has lead to a new active request, {@code false}
+ * otherwise.
+ */
+ private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) {
+ if (requestsToCancel.isEmpty()) {
+ return false;
+ }
+
+ OverrideRequest prevActiveRequest = getLast(mRequests);
+ boolean causedNewRequestToBecomeActive = false;
+ mRequests.removeAll(requestsToCancel);
+ mStickyRequests.removeAll(requestsToCancel);
+ if (!mRequests.isEmpty()) {
+ OverrideRequest newActiveRequest = getLast(mRequests);
+ if (newActiveRequest != prevActiveRequest) {
+ mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE);
+ causedNewRequestToBecomeActive = true;
+ }
+ }
+
+ for (int i = 0; i < requestsToCancel.size(); i++) {
+ mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED);
+ }
+ return causedNewRequestToBecomeActive;
+ }
+
+ private int getRequestIndex(@NonNull IBinder token) {
+ final int numberOfRequests = mRequests.size();
+ if (numberOfRequests == 0) {
+ return -1;
+ }
+
+ for (int i = 0; i < numberOfRequests; i++) {
+ OverrideRequest request = mRequests.get(i);
+ if (request.getToken() == token) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Nullable
+ private static <T> T getLast(List<T> list) {
+ return list.size() > 0 ? list.get(list.size() - 1) : null;
+ }
+
+ private static boolean contains(int[] array, int value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public interface StatusChangeListener {
+ /**
+ * Notifies the listener of a change in request status. If a change within the controller
+ * causes one request to become active and one to become either suspended or cancelled, this
+ * method is guaranteed to be called with the active request first before the suspended or
+ * cancelled request.
+ */
+ void onStatusChanged(@NonNull OverrideRequest request, @RequestStatus int newStatus);
+ }
+}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 1acd5d097525..9dd2f8408c56 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -111,8 +111,8 @@ class DeviceStateToLayoutMap {
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
- d.getIsDefault(),
- d.getEnabled());
+ d.isDefaultDisplay(),
+ d.isEnabled());
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 35f29579b417..806bcc29f305 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -16,7 +16,9 @@
package com.android.server.display;
+import android.annotation.Nullable;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayViewport;
import android.os.IBinder;
@@ -43,6 +45,7 @@ abstract class DisplayDevice {
// The display device does not manage these properties itself, they are set by
// the display manager service. The display device shouldn't really be looking at these.
private int mCurrentLayerStack = -1;
+ private int mCurrentFlags = 0;
private int mCurrentOrientation = -1;
private Rect mCurrentLayerStackRect;
private Rect mCurrentDisplayRect;
@@ -104,6 +107,34 @@ abstract class DisplayDevice {
}
/**
+ * Returns the window token of the level of the WindowManager hierarchy to mirror, or null
+ * if layer mirroring by SurfaceFlinger should not be performed.
+ * For now, only used for mirroring started from MediaProjection.
+ */
+ @Nullable
+ public IBinder getWindowTokenClientToMirrorLocked() {
+ return null;
+ }
+
+ /**
+ * Updates the window token of the level of the level of the WindowManager hierarchy to mirror.
+ * If windowToken is null, then no layer mirroring by SurfaceFlinger to should be performed.
+ * For now, only used for mirroring started from MediaProjection.
+ */
+ public void setWindowTokenClientToMirrorLocked(IBinder windowToken) {
+ }
+
+ /**
+ * Returns the default size of the surface associated with the display, or null if the surface
+ * is not provided for layer mirroring by SurfaceFlinger.
+ * For now, only used for mirroring started from MediaProjection.
+ */
+ @Nullable
+ public Point getDisplaySurfaceDefaultSize() {
+ return null;
+ }
+
+ /**
* Gets the name of the display device.
*
* @return The display device name.
@@ -212,6 +243,19 @@ abstract class DisplayDevice {
}
/**
+ * Sets the display flags while in a transaction.
+ *
+ * Valid display flags:
+ * {@link SurfaceControl#DISPLAY_RECEIVES_INPUT}
+ */
+ public final void setDisplayFlagsLocked(SurfaceControl.Transaction t, int flags) {
+ if (mCurrentFlags != flags) {
+ mCurrentFlags = flags;
+ t.setDisplayFlags(mDisplayToken, flags);
+ }
+ }
+
+ /**
* Sets the display projection while in a transaction.
*
* @param orientation defines the display's orientation
@@ -298,6 +342,7 @@ abstract class DisplayDevice {
pw.println("mUniqueId=" + mUniqueId);
pw.println("mDisplayToken=" + mDisplayToken);
pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
+ pw.println("mCurrentFlags=" + mCurrentFlags);
pw.println("mCurrentOrientation=" + mCurrentOrientation);
pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect);
pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 57f44864d2c0..2b52350f0634 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -123,6 +123,17 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener {
return null;
}
+ // String uniqueId -> DisplayDevice object with that given uniqueId
+ public DisplayDevice getByUniqueIdLocked(@NonNull String uniqueId) {
+ for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
+ final DisplayDevice displayDevice = mDisplayDevices.get(i);
+ if (displayDevice.getUniqueId().equals(uniqueId)) {
+ return displayDevice;
+ }
+ }
+ return null;
+ }
+
private void handleDisplayDeviceAdded(DisplayDevice device) {
synchronized (mSyncRoot) {
DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1dc83f6f829b..a432d5bf081c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -202,7 +202,7 @@ public final class DisplayManagerService extends SystemService {
private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
private static final int MSG_REQUEST_TRAVERSAL = 4;
private static final int MSG_UPDATE_VIEWPORT = 5;
- private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
+ private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATIONS = 6;
private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
@@ -536,16 +536,29 @@ public final class DisplayManagerService extends SystemService {
final int newUserId = to.getUserIdentifier();
final int userSerial = getUserManager().getUserSerialNumber(newUserId);
synchronized (mSyncRoot) {
- final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
- Display.DEFAULT_DISPLAY);
- if (mCurrentUserId != newUserId) {
+ boolean userSwitching = mCurrentUserId != newUserId;
+ if (userSwitching) {
mCurrentUserId = newUserId;
- BrightnessConfiguration config =
- mPersistentDataStore.getBrightnessConfiguration(userSerial);
- displayPowerController.setBrightnessConfiguration(config);
- handleSettingsChange();
}
- displayPowerController.onSwitchUser(newUserId);
+ mLogicalDisplayMapper.forEachLocked(logicalDisplay -> {
+ if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
+ return;
+ }
+ final DisplayPowerController dpc = mDisplayPowerControllers.get(
+ logicalDisplay.getDisplayIdLocked());
+ if (dpc == null) {
+ return;
+ }
+ if (userSwitching) {
+ BrightnessConfiguration config =
+ getBrightnessConfigForDisplayWithPdsFallbackLocked(
+ logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(),
+ userSerial);
+ dpc.setBrightnessConfiguration(config);
+ }
+ dpc.onSwitchUser(newUserId);
+ });
+ handleSettingsChange();
}
}
@@ -1328,6 +1341,13 @@ public final class DisplayManagerService extends SystemService {
if (work != null) {
mHandler.post(work);
}
+ final int displayId = display.getDisplayIdLocked();
+ DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+ if (dpc != null) {
+ dpc.onDisplayChanged();
+ }
+ mPersistentDataStore.saveIfNeeded();
+ mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS);
handleLogicalDisplayChangedLocked(display);
}
@@ -1436,24 +1456,42 @@ public final class DisplayManagerService extends SystemService {
return mDisplayModeDirector.getModeSwitchingType();
}
- private void setBrightnessConfigurationForUserInternal(
- @Nullable BrightnessConfiguration c, @UserIdInt int userId,
- @Nullable String packageName) {
+ private void setBrightnessConfigurationForDisplayInternal(
+ @Nullable BrightnessConfiguration c, String uniqueId, @UserIdInt int userId,
+ String packageName) {
validateBrightnessConfiguration(c);
final int userSerial = getUserManager().getUserSerialNumber(userId);
synchronized (mSyncRoot) {
try {
- mPersistentDataStore.setBrightnessConfigurationForUser(c, userSerial,
- packageName);
+ DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId);
+ if (displayDevice == null) {
+ return;
+ }
+ mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice,
+ userSerial, packageName);
} finally {
mPersistentDataStore.saveIfNeeded();
}
- if (userId == mCurrentUserId) {
- mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY).setBrightnessConfiguration(c);
+ if (userId != mCurrentUserId) {
+ return;
+ }
+ DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId);
+ if (dpc != null) {
+ dpc.setBrightnessConfiguration(c);
}
}
}
+ private DisplayPowerController getDpcFromUniqueIdLocked(String uniqueId) {
+ final DisplayDevice displayDevice = mDisplayDeviceRepo.getByUniqueIdLocked(uniqueId);
+ final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayDevice);
+ if (logicalDisplay != null) {
+ final int displayId = logicalDisplay.getDisplayIdLocked();
+ return mDisplayPowerControllers.get(displayId);
+ }
+ return null;
+ }
+
@VisibleForTesting
void validateBrightnessConfiguration(BrightnessConfiguration config) {
if (config == null) {
@@ -1476,13 +1514,22 @@ public final class DisplayManagerService extends SystemService {
return false;
}
- private void loadBrightnessConfiguration() {
+ private void loadBrightnessConfigurations() {
+ int userSerial = getUserManager().getUserSerialNumber(mContext.getUserId());
synchronized (mSyncRoot) {
- final int userSerial = getUserManager().getUserSerialNumber(mCurrentUserId);
- BrightnessConfiguration config =
- mPersistentDataStore.getBrightnessConfiguration(userSerial);
- mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY).setBrightnessConfiguration(
- config);
+ mLogicalDisplayMapper.forEachLocked((logicalDisplay) -> {
+ final String uniqueId =
+ logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ final BrightnessConfiguration config =
+ getBrightnessConfigForDisplayWithPdsFallbackLocked(uniqueId, userSerial);
+ if (config != null) {
+ final DisplayPowerController dpc = mDisplayPowerControllers.get(
+ logicalDisplay.getDisplayIdLocked());
+ if (dpc != null) {
+ dpc.setBrightnessConfiguration(config);
+ }
+ }
+ });
}
}
@@ -1693,9 +1740,17 @@ public final class DisplayManagerService extends SystemService {
return SurfaceControl.getDisplayedContentSample(token, maxFrames, timestamp);
}
- void resetBrightnessConfiguration() {
- setBrightnessConfigurationForUserInternal(null, mContext.getUserId(),
+ void resetBrightnessConfigurations() {
+ mPersistentDataStore.setBrightnessConfigurationForUser(null, mContext.getUserId(),
mContext.getPackageName());
+ mLogicalDisplayMapper.forEachLocked((logicalDisplay -> {
+ if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
+ return;
+ }
+ final String uniqueId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ setBrightnessConfigurationForDisplayInternal(null, uniqueId, mContext.getUserId(),
+ mContext.getPackageName());
+ }));
}
void setAutoBrightnessLoggingEnabled(boolean enabled) {
@@ -1762,10 +1817,13 @@ public final class DisplayManagerService extends SystemService {
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
final boolean ownContent = (info.flags & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
+ // Mirror the part of WM hierarchy that corresponds to the provided window token.
+ IBinder windowTokenClientToMirror = device.getWindowTokenClientToMirrorLocked();
+
// Find the logical display that the display device is showing.
// Certain displays only ever show their own content.
LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
- if (!ownContent) {
+ if (!ownContent && windowTokenClientToMirror == null) {
if (display != null && !display.hasContentLocked()) {
// If the display does not have any content of its own, then
// automatically mirror the requested logical display contents if possible.
@@ -2005,9 +2063,6 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mLogicalDisplayMapper.dumpLocked(pw);
- pw.println();
- mDisplayModeDirector.dump(pw);
-
final int callbackCount = mCallbacks.size();
pw.println();
pw.println("Callbacks: size=" + callbackCount);
@@ -2030,6 +2085,8 @@ public final class DisplayManagerService extends SystemService {
pw.println();
mPersistentDataStore.dump(pw);
}
+ pw.println();
+ mDisplayModeDirector.dump(pw);
synchronized (mSyncDump) {
mDumpInProgress = false;
}
@@ -2137,6 +2194,18 @@ public final class DisplayManagerService extends SystemService {
return display == null ? null : display.getPrimaryDisplayDeviceLocked();
}
+ private BrightnessConfiguration getBrightnessConfigForDisplayWithPdsFallbackLocked(
+ String uniqueId, int userSerial) {
+ BrightnessConfiguration config =
+ mPersistentDataStore.getBrightnessConfigurationForDisplayLocked(
+ uniqueId, userSerial);
+ if (config == null) {
+ // Get from global configurations
+ config = mPersistentDataStore.getBrightnessConfiguration(userSerial);
+ }
+ return config;
+ }
+
private final class DisplayManagerHandler extends Handler {
public DisplayManagerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -2178,8 +2247,8 @@ public final class DisplayManagerService extends SystemService {
break;
}
- case MSG_LOAD_BRIGHTNESS_CONFIGURATION:
- loadBrightnessConfiguration();
+ case MSG_LOAD_BRIGHTNESS_CONFIGURATIONS:
+ loadBrightnessConfigurations();
break;
case MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
@@ -2188,6 +2257,9 @@ public final class DisplayManagerService extends SystemService {
int displayId = msg.arg1;
final LogicalDisplay display =
mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (display == null) {
+ break;
+ }
uids = display.getPendingFrameRateOverrideUids();
display.clearPendingFrameRateOverrideUids();
}
@@ -2801,21 +2873,49 @@ public final class DisplayManagerService extends SystemService {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS,
"Permission required to change the display brightness"
- + " configuration of another user");
+ + " configuration of another user");
}
- if (packageName != null && !validatePackageName(getCallingUid(), packageName)) {
- packageName = null;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ mLogicalDisplayMapper.forEachLocked(logicalDisplay -> {
+ if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
+ return;
+ }
+ final DisplayDevice displayDevice =
+ logicalDisplay.getPrimaryDisplayDeviceLocked();
+ setBrightnessConfigurationForDisplayInternal(c, displayDevice.getUniqueId(),
+ userId, packageName);
+ });
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void setBrightnessConfigurationForDisplay(BrightnessConfiguration c,
+ String uniqueId, int userId, String packageName) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS,
+ "Permission required to change the display's brightness configuration");
+ if (userId != UserHandle.getCallingUserId()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS,
+ "Permission required to change the display brightness"
+ + " configuration of another user");
}
final long token = Binder.clearCallingIdentity();
try {
- setBrightnessConfigurationForUserInternal(c, userId, packageName);
+ setBrightnessConfigurationForDisplayInternal(c, uniqueId, userId, packageName);
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override // Binder call
- public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) {
+ public BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueId,
+ int userId) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS,
"Permission required to read the display's brightness configuration");
@@ -2826,14 +2926,19 @@ public final class DisplayManagerService extends SystemService {
+ " configuration of another user");
}
final long token = Binder.clearCallingIdentity();
+ final int userSerial = getUserManager().getUserSerialNumber(userId);
try {
- final int userSerial = getUserManager().getUserSerialNumber(userId);
synchronized (mSyncRoot) {
+ // Get from per-display configurations
BrightnessConfiguration config =
- mPersistentDataStore.getBrightnessConfiguration(userSerial);
+ getBrightnessConfigForDisplayWithPdsFallbackLocked(
+ uniqueId, userSerial);
if (config == null) {
- config = mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
- .getDefaultBrightnessConfiguration();
+ // Get default configuration
+ DisplayPowerController dpc = getDpcFromUniqueIdLocked(uniqueId);
+ if (dpc != null) {
+ config = dpc.getDefaultBrightnessConfiguration();
+ }
}
return config;
}
@@ -2842,6 +2947,21 @@ public final class DisplayManagerService extends SystemService {
}
}
+
+
+ @Override // Binder call
+ public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) {
+ final String uniqueId;
+ synchronized (mSyncRoot) {
+ DisplayDevice displayDevice = mLogicalDisplayMapper.getDisplayLocked(
+ Display.DEFAULT_DISPLAY).getPrimaryDisplayDeviceLocked();
+ uniqueId = displayDevice.getUniqueId();
+ }
+ return getBrightnessConfigurationForDisplay(uniqueId, userId);
+
+
+ }
+
@Override // Binder call
public BrightnessConfiguration getDefaultBrightnessConfiguration() {
mContext.enforceCallingOrSelfPermission(
@@ -3110,7 +3230,7 @@ public final class DisplayManagerService extends SystemService {
initializeDisplayPowerControllersLocked();
}
- mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATION);
+ mHandler.sendEmptyMessage(MSG_LOAD_BRIGHTNESS_CONFIGURATIONS);
}
@Override
@@ -3338,6 +3458,40 @@ public final class DisplayManagerService extends SystemService {
}
return config.getRefreshRateLimitations();
}
+
+ @Override
+ public IBinder getWindowTokenClientToMirror(int displayId) {
+ final DisplayDevice device;
+ synchronized (mSyncRoot) {
+ device = getDeviceForDisplayLocked(displayId);
+ if (device == null) {
+ return null;
+ }
+ }
+ return device.getWindowTokenClientToMirrorLocked();
+ }
+
+ @Override
+ public void setWindowTokenClientToMirror(int displayId, IBinder windowToken) {
+ synchronized (mSyncRoot) {
+ final DisplayDevice device = getDeviceForDisplayLocked(displayId);
+ if (device != null) {
+ device.setWindowTokenClientToMirrorLocked(windowToken);
+ }
+ }
+ }
+
+ @Override
+ public Point getDisplaySurfaceDefaultSize(int displayId) {
+ final DisplayDevice device;
+ synchronized (mSyncRoot) {
+ device = getDeviceForDisplayLocked(displayId);
+ if (device == null) {
+ return null;
+ }
+ }
+ return device.getDisplaySurfaceDefaultSize();
+ }
}
class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 48edb73ac81d..158c8f06d13e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -115,7 +115,7 @@ class DisplayManagerShellCommand extends ShellCommand {
}
private int resetBrightnessConfiguration() {
- mService.resetBrightnessConfiguration();
+ mService.resetBrightnessConfigurations();
return 0;
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c8f654c5f5c7..ae51dd787bf5 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -41,7 +41,6 @@ import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -621,8 +620,8 @@ public class DisplayModeDirector {
mHbmObserver.dumpLocked(pw);
mSkinThermalStatusObserver.dumpLocked(pw);
}
- //mLock not needed to collect sensor dump
- mSensorObserver.dumpLocked(pw);
+
+ mSensorObserver.dump(pw);
}
private void updateVoteLocked(int priority, Vote vote) {
@@ -2242,7 +2241,7 @@ public class DisplayModeDirector {
}
}
- void dumpLocked(PrintWriter pw) {
+ void dump(PrintWriter pw) {
pw.println(" SensorObserver");
pw.println(" mIsProxActive=" + mIsProxActive);
pw.println(" mDozeStateByDisplay:");
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 81cbbff44778..c5363437f878 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -108,8 +108,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// screen state returns. Playing the animation can also be somewhat slow.
private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
- // The minimum reduction in brightness when dimmed.
- private static final float SCREEN_DIM_MINIMUM_REDUCTION_FLOAT = 0.04f;
private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f;
private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
@@ -200,6 +198,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// The dim screen brightness.
private final float mScreenBrightnessDimConfig;
+ // The minimum dim amount to use if the screen brightness is already below
+ // mScreenBrightnessDimConfig.
+ private final float mScreenBrightnessMinimumDimAmount;
+
private final float mScreenBrightnessDefault;
// The minimum allowed brightness while in VR.
@@ -378,6 +380,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private float mInitialAutoBrightness;
// The controller for the automatic brightness level.
+ @Nullable
private AutomaticBrightnessController mAutomaticBrightnessController;
private Sensor mLightSensor;
@@ -484,6 +487,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
mScreenBrightnessDimConfig = clampAbsoluteBrightness(
pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+ mScreenBrightnessMinimumDimAmount = resources.getFloat(
+ com.android.internal.R.dimen.config_screenBrightnessMinimumDimAmountFloat);
+
// NORMAL SCREEN SETTINGS
mScreenBrightnessDefault = clampAbsoluteBrightness(
@@ -608,7 +614,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mPendingRbcOnOrChanged = strengthChanged || justActivated;
// Reset model if strength changed OR rbc is turned off
- if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) {
+ if ((strengthChanged || !justActivated) && mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
}
@@ -739,13 +745,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
final IBinder token = device.getDisplayTokenLocked();
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
mHandler.post(() -> {
- if (mDisplayDevice == device) {
- return;
+ if (mDisplayDevice != device) {
+ mDisplayDevice = device;
+ mUniqueDisplayId = uniqueId;
+ mDisplayDeviceConfig = config;
+ loadFromDisplayDeviceConfig(token, info);
+ updatePowerState();
}
- mDisplayDevice = device;
- mUniqueDisplayId = uniqueId;
- mDisplayDeviceConfig = config;
- loadFromDisplayDeviceConfig(token, info);
});
}
@@ -1052,11 +1058,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
assert(state != Display.STATE_UNKNOWN);
- // Initialize things the first time the power state is changed.
- if (mustInitialize) {
- initialize(state);
- }
-
// Apply the proximity sensor.
if (mProximitySensor != null) {
if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
@@ -1107,6 +1108,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
state = Display.STATE_OFF;
}
+ // Initialize things the first time the power state is changed.
+ if (mustInitialize) {
+ initialize(state);
+ }
+
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
@@ -1283,7 +1289,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
brightnessState = Math.max(
- Math.min(brightnessState - SCREEN_DIM_MINIMUM_REDUCTION_FLOAT,
+ Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
mScreenBrightnessDimConfig),
PowerManager.BRIGHTNESS_MIN);
mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
@@ -1568,7 +1574,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
sendUpdatePowerStateLocked();
mHandler.post(mOnBrightnessChangeRunnable);
// TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
- mAutomaticBrightnessController.update();
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.update();
+ }
}, mContext);
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index a823f3e837f3..526e8e11f555 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -603,14 +603,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
&& SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
}
- if (res.getBoolean(
- com.android.internal.R.bool.config_maskMainBuiltInDisplayCutout)) {
- mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
- }
- mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
- mInfo.width, mInfo.height);
- mInfo.roundedCorners = RoundedCorners.fromResources(
- res, mInfo.width, mInfo.height);
} else if (isBuiltIn) {
mInfo.type = Display.TYPE_INTERNAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
@@ -642,6 +634,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
}
}
+ if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
+ }
+ mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+ mInfo.uniqueId, mInfo.width, mInfo.height);
+
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.uniqueId, mInfo.width, mInfo.height);
+
if (mStaticDisplayInfo.isInternal) {
mInfo.type = Display.TYPE_INTERNAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 9acb4c8f471a..5186744d5c27 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static com.android.server.display.DisplayDeviceInfo.TOUCH_NONE;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -512,6 +514,11 @@ final class LogicalDisplay {
boolean isBlanked) {
// Set the layer stack.
device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);
+ // Also inform whether the device is the same one sent to inputflinger for its layerstack.
+ // TODO(b/188914255): Remove once input can dispatch against device vs layerstack.
+ device.setDisplayFlagsLocked(t,
+ device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE
+ ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0);
// Set the color mode and allowed display mode.
if (device == mPrimaryDisplayDevice) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 3364e5e148d1..4ff3db825003 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -22,6 +22,8 @@ import android.hardware.devicestate.DeviceStateManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
@@ -68,7 +70,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
- private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 500;
+ private static final int TIMEOUT_STATE_TRANSITION_MILLIS = 300;
private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1;
@@ -97,6 +99,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final boolean mSupportsConcurrentInternalDisplays;
/**
+ * Wake the device when transitioning into this device state.
+ */
+ private final int mDeviceStateOnWhichToWakeUp;
+
+ /**
* Map of all logical displays indexed by logical display id.
* Any modification to mLogicalDisplays must invalidate the DisplayManagerGlobal cache.
* TODO: multi-display - Move the aforementioned comment?
@@ -113,6 +120,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private final Listener mListener;
private final DisplayManagerService.SyncRoot mSyncRoot;
private final LogicalDisplayMapperHandler mHandler;
+ private final PowerManager mPowerManager;
/**
* Has an entry for every logical display that the rest of the system has been notified about.
@@ -150,12 +158,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
mSyncRoot = syncRoot;
+ mPowerManager = context.getSystemService(PowerManager.class);
mHandler = new LogicalDisplayMapperHandler(handler.getLooper());
mDisplayDeviceRepo = repo;
mListener = listener;
mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(
com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);
+ mDeviceStateOnWhichToWakeUp = context.getResources().getInteger(
+ com.android.internal.R.integer.config_deviceStateOnWhichToWakeUp);
mDisplayDeviceRepo.addListener(this);
mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
}
@@ -197,6 +208,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
public LogicalDisplay getDisplayLocked(DisplayDevice device) {
+ if (device == null) {
+ return null;
+ }
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
final LogicalDisplay display = mLogicalDisplays.valueAt(i);
@@ -260,6 +274,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.println("mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
ipw.println("mCurrentLayout=" + mCurrentLayout);
+ ipw.println("mDeviceStateOnWhichToWakeUp=" + mDeviceStateOnWhichToWakeUp);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
@@ -277,7 +292,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
void setDeviceStateLocked(int state) {
- Slog.i(TAG, "Requesting Transition to state: " + state);
+ final boolean isInteractive = mPowerManager.isInteractive();
+ Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+ + ", interactive=" + isInteractive);
// As part of a state transition, we may need to turn off some displays temporarily so that
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
@@ -286,8 +303,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
}
mPendingDeviceState = state;
- if (areAllTransitioningDisplaysOffLocked()) {
- // Nothing to wait on, we're good to go
+ final boolean wakeDevice = mPendingDeviceState == mDeviceStateOnWhichToWakeUp
+ && !isInteractive;
+
+ // If all displays are off already, we can just transition here, unless the device is asleep
+ // and we plan on waking it up. In that case, fall through to the call to wakeUp, and defer
+ // the final transition until later once the device is awake.
+ if (areAllTransitioningDisplaysOffLocked() && !wakeDevice) {
transitionToPendingStateLocked();
return;
}
@@ -298,6 +320,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// Send the transitioning phase updates to DisplayManager so that the displays can
// start turning OFF in preparation for the new layout.
updateLogicalDisplaysLocked();
+
+ if (wakeDevice) {
+ // We already told the displays to turn off, now we need to wake the device as
+ // we transition to this new state. We do it here so that the waking happens between the
+ // transition from one layout to another.
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_UNFOLD_DEVICE,
+ "server.display:unfold");
+ }
mHandler.sendEmptyMessageDelayed(MSG_TRANSITION_TO_PENDING_DEVICE_STATE,
TIMEOUT_STATE_TRANSITION_MILLIS);
}
@@ -424,6 +454,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
assignDisplayGroupLocked(display);
mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+ // The display is involved in a display layout transition
} else if (updateState == UPDATE_STATE_TRANSITION) {
mLogicalDisplaysToUpdate.put(displayId,
LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
@@ -614,14 +645,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// We consider a display-device as changing/transition if
// 1) It's already marked as transitioning
- // 2) It's going from enabled to disabled
+ // 2) It's going from enabled to disabled, or vice versa
// 3) It's enabled, but it's mapped to a new logical display ID. To the user this
// would look like apps moving from one screen to another since task-stacks stay
// with the logical display [ID].
final boolean isTransitioning =
(logicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION)
- || (wasEnabled && !willBeEnabled)
- || (wasEnabled && deviceHasNewLogicalDisplayId);
+ || (wasEnabled != willBeEnabled)
+ || deviceHasNewLogicalDisplayId;
if (isTransitioning) {
setDisplayPhase(logicalDisplay, phase);
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index c90ddf48a091..4b0d43b3d1d4 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -63,6 +63,15 @@ import java.util.Objects;
* &lt;display unique-id="XXXXXXX">
* &lt;color-mode>0&lt;/color-mode>
* &lt;brightness-value>0&lt;/brightness-value>
+ * &lt;brightness-configurations>
+ * &lt;brightness-configuration user-serial="0" package-name="com.example"
+ * timestamp="1234">
+ * &lt;brightness-curve description="some text">
+ * &lt;brightness-point lux="0" nits="13.25"/>
+ * &lt;brightness-point lux="20" nits="35.94"/>
+ * &lt;/brightness-curve>
+ * &lt;/brightness-configuration>
+ * &lt;/brightness-configurations>
* &lt;/display>
* &lt;/display-states>
* &lt;stable-device-values>
@@ -120,7 +129,8 @@ final class PersistentDataStore {
private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
// Brightness configuration by user
- private BrightnessConfigurations mBrightnessConfigurations = new BrightnessConfigurations();
+ private BrightnessConfigurations mGlobalBrightnessConfigurations =
+ new BrightnessConfigurations();
// True if the data has been loaded.
private boolean mLoaded;
@@ -293,18 +303,44 @@ final class PersistentDataStore {
}
}
+ // Used for testing & reset
public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial,
@Nullable String packageName) {
loadIfNeeded();
- if (mBrightnessConfigurations.setBrightnessConfigurationForUser(c, userSerial,
+ if (mGlobalBrightnessConfigurations.setBrightnessConfigurationForUser(c, userSerial,
packageName)) {
+
setDirty();
}
}
+ public boolean setBrightnessConfigurationForDisplayLocked(BrightnessConfiguration configuration,
+ DisplayDevice device, int userSerial, String packageName) {
+ if (device == null || !device.hasStableUniqueId()) {
+ return false;
+ }
+ DisplayState state = getDisplayState(device.getUniqueId(), /*createIfAbsent*/ true);
+ if (state.setBrightnessConfiguration(configuration, userSerial, packageName)) {
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
+
+ public BrightnessConfiguration getBrightnessConfigurationForDisplayLocked(
+ String uniqueDisplayId, int userSerial) {
+ loadIfNeeded();
+ DisplayState state = mDisplayStates.get(uniqueDisplayId);
+ if (state != null) {
+ return state.getBrightnessConfiguration(userSerial);
+ }
+ return null;
+ }
+
public BrightnessConfiguration getBrightnessConfiguration(int userSerial) {
loadIfNeeded();
- return mBrightnessConfigurations.getBrightnessConfiguration(userSerial);
+ return mGlobalBrightnessConfigurations.getBrightnessConfiguration(userSerial);
}
private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
@@ -391,7 +427,7 @@ final class PersistentDataStore {
mStableDeviceValues.loadFromXml(parser);
}
if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) {
- mBrightnessConfigurations.loadFromXml(parser);
+ mGlobalBrightnessConfigurations.loadFromXml(parser);
}
}
}
@@ -470,7 +506,7 @@ final class PersistentDataStore {
mStableDeviceValues.saveToXml(serializer);
serializer.endTag(null, TAG_STABLE_DEVICE_VALUES);
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
- mBrightnessConfigurations.saveToXml(serializer);
+ mGlobalBrightnessConfigurations.saveToXml(serializer);
serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE);
serializer.endDocument();
@@ -493,14 +529,18 @@ final class PersistentDataStore {
}
pw.println(" StableDeviceValues:");
mStableDeviceValues.dump(pw, " ");
- pw.println(" BrightnessConfigurations:");
- mBrightnessConfigurations.dump(pw, " ");
+ pw.println(" GlobalBrightnessConfigurations:");
+ mGlobalBrightnessConfigurations.dump(pw, " ");
}
private static final class DisplayState {
private int mColorMode;
private float mBrightness;
+ // Brightness configuration by user
+ private BrightnessConfigurations mDisplayBrightnessConfigurations =
+ new BrightnessConfigurations();
+
public boolean setColorMode(int colorMode) {
if (colorMode == mColorMode) {
return false;
@@ -525,6 +565,16 @@ final class PersistentDataStore {
return mBrightness;
}
+ public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
+ int userSerial, String packageName) {
+ mDisplayBrightnessConfigurations.setBrightnessConfigurationForUser(
+ configuration, userSerial, packageName);
+ return true;
+ }
+
+ public BrightnessConfiguration getBrightnessConfiguration(int userSerial) {
+ return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial);
+ }
public void loadFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
@@ -540,6 +590,9 @@ final class PersistentDataStore {
String brightness = parser.nextText();
mBrightness = Float.parseFloat(brightness);
break;
+ case TAG_BRIGHTNESS_CONFIGURATIONS:
+ mDisplayBrightnessConfigurations.loadFromXml(parser);
+ break;
}
}
}
@@ -548,15 +601,21 @@ final class PersistentDataStore {
serializer.startTag(null, TAG_COLOR_MODE);
serializer.text(Integer.toString(mColorMode));
serializer.endTag(null, TAG_COLOR_MODE);
+
serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
serializer.text(Float.toString(mBrightness));
serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
+ serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+ mDisplayBrightnessConfigurations.saveToXml(serializer);
+ serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
}
public void dump(final PrintWriter pw, final String prefix) {
pw.println(prefix + "ColorMode=" + mColorMode);
pw.println(prefix + "BrightnessValue=" + mBrightness);
+ pw.println(prefix + "DisplayBrightnessConfigurations: ");
+ mDisplayBrightnessConfigurations.dump(pw, prefix);
}
}
@@ -621,11 +680,11 @@ final class PersistentDataStore {
private static final class BrightnessConfigurations {
// Maps from a user ID to the users' given brightness configuration
- private SparseArray<BrightnessConfiguration> mConfigurations;
+ private final SparseArray<BrightnessConfiguration> mConfigurations;
// Timestamp of time the configuration was set.
- private SparseLongArray mTimeStamps;
+ private final SparseLongArray mTimeStamps;
// Package that set the configuration.
- private SparseArray<String> mPackageNames;
+ private final SparseArray<String> mPackageNames;
public BrightnessConfigurations() {
mConfigurations = new SparseArray<>();
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index b7931c8a8424..a59219265e2c 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -31,7 +31,9 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUST
import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
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.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.media.projection.IMediaProjection;
@@ -231,6 +233,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private Display.Mode mMode;
private boolean mIsDisplayOn;
private int mDisplayIdToMirror;
+ private IBinder mWindowTokenClientToMirror;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -253,6 +256,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mUniqueIndex = uniqueIndex;
mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
+ mWindowTokenClientToMirror = virtualDisplayConfig.getWindowTokenClientToMirror();
}
@Override
@@ -282,6 +286,29 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
return mDisplayIdToMirror;
}
+ @Override
+ @Nullable
+ public IBinder getWindowTokenClientToMirrorLocked() {
+ return mWindowTokenClientToMirror;
+ }
+
+ @Override
+ public void setWindowTokenClientToMirrorLocked(IBinder windowToken) {
+ if (mWindowTokenClientToMirror != windowToken) {
+ mWindowTokenClientToMirror = windowToken;
+ sendDisplayDeviceEventLocked(this, DISPLAY_DEVICE_EVENT_CHANGED);
+ sendTraversalRequestLocked();
+ }
+ }
+
+ @Override
+ public Point getDisplaySurfaceDefaultSize() {
+ if (mSurface == null) {
+ return null;
+ }
+ return mSurface.getDefaultSize();
+ }
+
@VisibleForTesting
Surface getSurfaceLocked() {
return mSurface;
@@ -362,6 +389,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
pw.println("mDisplayState=" + Display.stateToString(mDisplayState));
pw.println("mStopped=" + mStopped);
pw.println("mDisplayIdToMirror=" + mDisplayIdToMirror);
+ pw.println("mWindowTokenClientToMirror=" + mWindowTokenClientToMirror);
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6fb9e58a49d1..fae7e451b529 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -189,7 +189,7 @@ public class InputManagerService extends IInputManager.Stub
private final InputManagerHandler mHandler;
// Context cache used for loading pointer resources.
- private Context mDisplayContext;
+ private Context mPointerIconDisplayContext;
private final File mDoubleTouchGestureEnableFile;
@@ -839,21 +839,31 @@ public class InputManagerService extends IInputManager.Stub
throw new IllegalArgumentException("mode is invalid");
}
if (ENABLE_PER_WINDOW_INPUT_ROTATION) {
- if (event instanceof MotionEvent) {
- final Context dispCtx = getContextForDisplay(event.getDisplayId());
- final Display display = dispCtx.getDisplay();
+ // Motion events that are pointer events or relative mouse events will need to have the
+ // inverse display rotation applied to them.
+ if (event instanceof MotionEvent
+ && (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
+ || event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE))) {
+ Context displayContext = getContextForDisplay(event.getDisplayId());
+ if (displayContext == null) {
+ displayContext = Objects.requireNonNull(
+ getContextForDisplay(Display.DEFAULT_DISPLAY));
+ }
+ final Display display = displayContext.getDisplay();
final int rotation = display.getRotation();
if (rotation != ROTATION_0) {
final MotionEvent motion = (MotionEvent) event;
// Injections are currently expected to be in the space of the injector (ie.
- // usually assumed to be post-rotated). Thus we need to unrotate into raw
+ // usually assumed to be post-rotated). Thus we need to un-rotate into raw
// input coordinates for dispatch.
final Point sz = new Point();
- display.getRealSize(sz);
- if ((rotation % 2) != 0) {
- final int tmpX = sz.x;
- sz.x = sz.y;
- sz.y = tmpX;
+ if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
+ display.getRealSize(sz);
+ if ((rotation % 2) != 0) {
+ final int tmpX = sz.x;
+ sz.x = sz.y;
+ sz.y = tmpX;
+ }
}
motion.applyTransform(MotionEvent.createRotateMatrix(
(4 - rotation), sz.x, sz.y));
@@ -890,6 +900,7 @@ public class InputManagerService extends IInputManager.Stub
@Override // Binder call
public VerifiedInputEvent verifyInputEvent(InputEvent event) {
+ Objects.requireNonNull(event, "event must not be null");
return nativeVerifyInputEvent(mPtr, event);
}
@@ -1742,6 +1753,11 @@ public class InputManagerService extends IInputManager.Stub
/** Clean up input window handles of the given display. */
public void onDisplayRemoved(int displayId) {
+ if (mPointerIconDisplayContext != null
+ && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
+ mPointerIconDisplayContext = null;
+ }
+
nativeDisplayRemoved(mPtr, displayId);
}
@@ -2971,24 +2987,43 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
private PointerIcon getPointerIcon(int displayId) {
- return PointerIcon.getDefaultIcon(getContextForDisplay(displayId));
+ return PointerIcon.getDefaultIcon(getContextForPointerIcon(displayId));
}
- private Context getContextForDisplay(int displayId) {
- if (mDisplayContext != null && mDisplayContext.getDisplay().getDisplayId() == displayId) {
- return mDisplayContext;
+ @NonNull
+ private Context getContextForPointerIcon(int displayId) {
+ if (mPointerIconDisplayContext != null
+ && mPointerIconDisplayContext.getDisplay().getDisplayId() == displayId) {
+ return mPointerIconDisplayContext;
+ }
+
+ // Create and cache context for non-default display.
+ mPointerIconDisplayContext = getContextForDisplay(displayId);
+
+ // Fall back to default display if the requested displayId does not exist.
+ if (mPointerIconDisplayContext == null) {
+ mPointerIconDisplayContext = getContextForDisplay(Display.DEFAULT_DISPLAY);
}
+ return mPointerIconDisplayContext;
+ }
+ @Nullable
+ private Context getContextForDisplay(int displayId) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ return null;
+ }
if (mContext.getDisplay().getDisplayId() == displayId) {
- mDisplayContext = mContext;
- return mDisplayContext;
+ return mContext;
}
- // Create and cache context for non-default display.
- final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ final DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
final Display display = displayManager.getDisplay(displayId);
- mDisplayContext = mContext.createDisplayContext(display);
- return mDisplayContext;
+ if (display == null) {
+ return null;
+ }
+
+ return mContext.createDisplayContext(display);
}
// Native callback.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3e52f5e07e62..1516739de99f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -542,9 +542,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
*/
private InputMethodSubtype mCurrentSubtype;
- // Was the keyguard locked when this client became current?
- private boolean mCurClientInKeyguard;
-
/**
* {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}
*/
@@ -2356,19 +2353,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (displayIdToShowIme == INVALID_DISPLAY) {
mImeHiddenByDisplayPolicy = true;
+ hideCurrentInputLocked(mCurFocusedWindow, 0, null,
+ SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
return InputBindResult.NO_IME;
}
mImeHiddenByDisplayPolicy = false;
if (mCurClient != cs) {
- // Was the keyguard locked when switching over to the new client?
- mCurClientInKeyguard = isKeyguardLocked();
// If the client is changing, we need to switch over to the new
// one.
unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT);
- if (DEBUG) Slog.v(TAG, "switching to client: client="
- + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
-
// If the screen is on, inform the new client it is active
if (mIsInteractive) {
scheduleSetActiveToClient(cs, true /* active */, false /* fullscreen */,
@@ -2528,9 +2522,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
// Dispatch display id for InputMethodService to update context display.
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(
- MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken,
- mMethodMap.get(mCurMethodId).getConfigChanges()));
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_INITIALIZE_IME,
+ mMethodMap.get(mCurMethodId).getConfigChanges(), mCurMethod, mCurToken));
scheduleNotifyImeUidToAudioService(mCurMethodUid);
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
@@ -2837,7 +2830,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken);
- if (targetWindow != null && mLastImeTargetWindow != targetWindow) {
+ if (targetWindow != null) {
mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
}
mLastImeTargetWindow = targetWindow;
@@ -2874,12 +2867,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// all updateSystemUi happens on system previlege.
final long ident = Binder.clearCallingIdentity();
try {
- // apply policy for binder calls
- if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
- vis = 0;
- }
if (!mCurPerceptible) {
- vis &= ~InputMethodService.IME_VISIBLE;
+ if ((vis & InputMethodService.IME_VISIBLE) != 0) {
+ vis &= ~InputMethodService.IME_VISIBLE;
+ vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
+ }
+ } else {
+ vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
}
// mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
@@ -4121,6 +4115,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ /** Called right after {@link IInputMethod#showSoftInput}. */
+ private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
+ @SoftInputShowHideReason int reason) {
+ final WindowManagerInternal.ImeTargetInfo info =
+ mWindowManagerInternal.onToggleImeRequested(
+ show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
+ mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
+ mCurFocusedWindowClient, mCurAttribute, info.focusedWindowName,
+ mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
+ info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName));
+ }
+
@BinderThread
private void hideMySoftInput(@NonNull IBinder token, int flags) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
@@ -4239,18 +4245,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+ args.arg3 + ", " + msg.arg1 + ", " + args.arg2 + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
+ final IBinder token = (IBinder) args.arg3;
((IInputMethod) args.arg1).showSoftInput(
- (IBinder) args.arg3, msg.arg1, (ResultReceiver) args.arg2);
- mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurAttribute,
- mWindowManagerInternal.getWindowName(mCurFocusedWindow),
- mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- mWindowManagerInternal.getWindowName(
- mShowRequestWindowMap.get(args.arg3)),
- mWindowManagerInternal.getImeControlTargetNameForLogging(
- mCurTokenDisplayId),
- mWindowManagerInternal.getImeTargetNameForLogging(
- mCurTokenDisplayId)));
+ token, msg.arg1 /* flags */, (ResultReceiver) args.arg2);
+ final IBinder requestToken = mShowRequestWindowMap.get(token);
+ onShowHideSoftInputRequested(true /* show */, requestToken, reason);
} catch (RemoteException e) {
}
args.recycle();
@@ -4262,18 +4261,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+ args.arg3 + ", " + args.arg2 + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
+ final IBinder token = (IBinder) args.arg3;
((IInputMethod)args.arg1).hideSoftInput(
- (IBinder) args.arg3, 0, (ResultReceiver)args.arg2);
- mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurAttribute,
- mWindowManagerInternal.getWindowName(mCurFocusedWindow),
- mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- mWindowManagerInternal.getWindowName(
- mHideRequestWindowMap.get(args.arg3)),
- mWindowManagerInternal.getImeControlTargetNameForLogging(
- mCurTokenDisplayId),
- mWindowManagerInternal.getImeTargetNameForLogging(
- mCurTokenDisplayId)));
+ token, 0 /* flags */, (ResultReceiver) args.arg2);
+ final IBinder requestToken = mHideRequestWindowMap.get(token);
+ onShowHideSoftInputRequested(false /* show */, requestToken, reason);
} catch (RemoteException e) {
}
args.recycle();
@@ -4290,12 +4282,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
try {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: "
- + msg.arg1);
+ + mCurTokenDisplayId);
}
final IBinder token = (IBinder) args.arg2;
- ((IInputMethod) args.arg1).initializeInternal(token, msg.arg1,
- new InputMethodPrivilegedOperationsImpl(this, token),
- (int) args.arg3);
+ ((IInputMethod) args.arg1).initializeInternal(token,
+ new InputMethodPrivilegedOperationsImpl(this, token), msg.arg1);
} catch (RemoteException e) {
}
args.recycle();
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index aa4fa7c6f470..4a41cb6d81e2 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -917,7 +917,7 @@ public final class MultiClientInputMethodManagerService {
.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
context, 0,
new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
- PendingIntent.FLAG_MUTABLE));
+ PendingIntent.FLAG_IMMUTABLE));
// Note: Instead of re-dispatching callback from the main thread to the worker thread
// where OnWorkerThreadCallback is running, we pass the Handler object here so that
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index a52c9cefb27d..5093f5dee55e 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -131,8 +131,8 @@ public class GeofenceManager extends
return mPermitted;
}
- boolean onLocationPermissionsChanged(String packageName) {
- if (getIdentity().getPackageName().equals(packageName)) {
+ boolean onLocationPermissionsChanged(@Nullable String packageName) {
+ if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -242,7 +242,7 @@ public class GeofenceManager extends
mLocationPermissionsListener =
new LocationPermissionsHelper.LocationPermissionsListener() {
@Override
- public void onLocationPermissionsChanged(String packageName) {
+ public void onLocationPermissionsChanged(@Nullable String packageName) {
GeofenceManager.this.onLocationPermissionsChanged(packageName);
}
@@ -494,7 +494,7 @@ public class GeofenceManager extends
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- void onLocationPermissionsChanged(String packageName) {
+ void onLocationPermissionsChanged(@Nullable String packageName) {
updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 5e6ae68c02f2..a54047665aba 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -119,8 +119,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
*/
protected void onGnssListenerUnregister() {}
- boolean onLocationPermissionsChanged(String packageName) {
- if (getIdentity().getPackageName().equals(packageName)) {
+ boolean onLocationPermissionsChanged(@Nullable String packageName) {
+ if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -197,7 +197,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
mLocationPermissionsListener =
new LocationPermissionsHelper.LocationPermissionsListener() {
@Override
- public void onLocationPermissionsChanged(String packageName) {
+ public void onLocationPermissionsChanged(@Nullable String packageName) {
GnssListenerMultiplexer.this.onLocationPermissionsChanged(packageName);
}
@@ -390,7 +390,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
- private void onLocationPermissionsChanged(String packageName) {
+ private void onLocationPermissionsChanged(@Nullable String packageName) {
updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 8460d6797543..1781588b0ba2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -52,8 +52,6 @@ public final class GnssMeasurementsProvider extends
private class GnssMeasurementListenerRegistration extends GnssListenerRegistration {
- private static final String GNSS_MEASUREMENTS_BUCKET = "gnss_measurement";
-
protected GnssMeasurementListenerRegistration(
@Nullable GnssMeasurementRequest request,
CallerIdentity callerIdentity,
@@ -70,15 +68,13 @@ public final class GnssMeasurementsProvider extends
@Nullable
@Override
protected void onActive() {
- mLocationAttributionHelper.reportHighPowerLocationStart(
- getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey());
+ mLocationAttributionHelper.reportHighPowerLocationStart(getIdentity());
}
@Nullable
@Override
protected void onInactive() {
- mLocationAttributionHelper.reportHighPowerLocationStop(
- getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey());
+ mLocationAttributionHelper.reportHighPowerLocationStop(getIdentity());
}
}
diff --git a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
index 5cb360be819a..483875230ac4 100644
--- a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
@@ -24,55 +24,23 @@ import static com.android.server.location.LocationManagerService.TAG;
import android.location.util.identity.CallerIdentity;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
/**
* Helps manage appop monitoring for multiple location clients.
*/
public class LocationAttributionHelper {
- private static class BucketKey {
- private final String mBucket;
- private final Object mKey;
-
- private BucketKey(String bucket, Object key) {
- mBucket = Objects.requireNonNull(bucket);
- mKey = Objects.requireNonNull(key);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- BucketKey that = (BucketKey) o;
- return mBucket.equals(that.mBucket)
- && mKey.equals(that.mKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mBucket, mKey);
- }
- }
-
private final AppOpsHelper mAppOpsHelper;
@GuardedBy("this")
- private final Map<CallerIdentity, Set<BucketKey>> mAttributions;
+ private final Map<CallerIdentity, Integer> mAttributions;
@GuardedBy("this")
- private final Map<CallerIdentity, Set<BucketKey>> mHighPowerAttributions;
+ private final Map<CallerIdentity, Integer> mHighPowerAttributions;
public LocationAttributionHelper(AppOpsHelper appOpsHelper) {
mAppOpsHelper = appOpsHelper;
@@ -84,15 +52,16 @@ public class LocationAttributionHelper {
/**
* Report normal location usage for the given caller in the given bucket, with a unique key.
*/
- public synchronized void reportLocationStart(CallerIdentity identity, String bucket,
- Object key) {
- Set<BucketKey> keySet = mAttributions.computeIfAbsent(identity,
- i -> new ArraySet<>());
- boolean empty = keySet.isEmpty();
- if (keySet.add(new BucketKey(bucket, key)) && empty) {
- if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) {
- mAttributions.remove(identity);
+ public synchronized void reportLocationStart(CallerIdentity identity) {
+ identity = CallerIdentity.forAggregation(identity);
+
+ int count = mAttributions.getOrDefault(identity, 0);
+ if (count == 0) {
+ if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) {
+ mAttributions.put(identity, 1);
}
+ } else {
+ mAttributions.put(identity, count + 1);
}
}
@@ -100,13 +69,15 @@ public class LocationAttributionHelper {
* Report normal location usage has stopped for the given caller in the given bucket, with a
* unique key.
*/
- public synchronized void reportLocationStop(CallerIdentity identity, String bucket,
- Object key) {
- Set<BucketKey> keySet = mAttributions.get(identity);
- if (keySet != null && keySet.remove(new BucketKey(bucket, key))
- && keySet.isEmpty()) {
+ public synchronized void reportLocationStop(CallerIdentity identity) {
+ identity = CallerIdentity.forAggregation(identity);
+
+ int count = mAttributions.getOrDefault(identity, 0);
+ if (count == 1) {
mAttributions.remove(identity);
mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity);
+ } else if (count > 1) {
+ mAttributions.put(identity, count - 1);
}
}
@@ -114,19 +85,19 @@ public class LocationAttributionHelper {
* Report high power location usage for the given caller in the given bucket, with a unique
* key.
*/
- public synchronized void reportHighPowerLocationStart(CallerIdentity identity, String bucket,
- Object key) {
- Set<BucketKey> keySet = mHighPowerAttributions.computeIfAbsent(identity,
- i -> new ArraySet<>());
- boolean empty = keySet.isEmpty();
- if (keySet.add(new BucketKey(bucket, key)) && empty) {
+ public synchronized void reportHighPowerLocationStart(CallerIdentity identity) {
+ identity = CallerIdentity.forAggregation(identity);
+
+ int count = mHighPowerAttributions.getOrDefault(identity, 0);
+ if (count == 0) {
+ if (D) {
+ Log.v(TAG, "starting high power location attribution for " + identity);
+ }
if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) {
- if (D) {
- Log.v(TAG, "starting high power location attribution for " + identity);
- }
- } else {
- mHighPowerAttributions.remove(identity);
+ mHighPowerAttributions.put(identity, 1);
}
+ } else {
+ mHighPowerAttributions.put(identity, count + 1);
}
}
@@ -134,16 +105,18 @@ public class LocationAttributionHelper {
* Report high power location usage has stopped for the given caller in the given bucket,
* with a unique key.
*/
- public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String bucket,
- Object key) {
- Set<BucketKey> keySet = mHighPowerAttributions.get(identity);
- if (keySet != null && keySet.remove(new BucketKey(bucket, key))
- && keySet.isEmpty()) {
+ public synchronized void reportHighPowerLocationStop(CallerIdentity identity) {
+ identity = CallerIdentity.forAggregation(identity);
+
+ int count = mHighPowerAttributions.getOrDefault(identity, 0);
+ if (count == 1) {
if (D) {
Log.v(TAG, "stopping high power location attribution for " + identity);
}
mHighPowerAttributions.remove(identity);
mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity);
+ } else if (count > 1) {
+ mHighPowerAttributions.put(identity, count - 1);
}
}
}
diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
index 2df21017156d..557ecda2ef4c 100644
--- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
@@ -18,6 +18,7 @@ package com.android.server.location.injector;
import static com.android.server.location.LocationPermissions.PERMISSION_NONE;
+import android.annotation.Nullable;
import android.location.util.identity.CallerIdentity;
import com.android.server.location.LocationPermissions;
@@ -36,9 +37,10 @@ public abstract class LocationPermissionsHelper {
public interface LocationPermissionsListener {
/**
- * Called when something has changed about location permissions for the given package.
+ * Called when something has changed about location permissions for the given package. A
+ * null package indicates this affects every package.
*/
- void onLocationPermissionsChanged(String packageName);
+ void onLocationPermissionsChanged(@Nullable String packageName);
/**
* Called when something has changed about location permissions for the given uid.
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 345dc217110b..9ed63b5ce2da 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -31,6 +31,7 @@ import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF
import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
+import static android.os.UserHandle.USER_CURRENT;
import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
@@ -177,7 +178,7 @@ public class LocationProviderManager extends
protected interface LocationTransport {
void deliverOnLocationChanged(LocationResult locationResult,
- @Nullable Runnable onCompleteCallback) throws Exception;
+ @Nullable IRemoteCallback onCompleteCallback) throws Exception;
void deliverOnFlushComplete(int requestCode) throws Exception;
}
@@ -197,9 +198,8 @@ public class LocationProviderManager extends
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
- @Nullable Runnable onCompleteCallback) throws RemoteException {
- mListener.onLocationChanged(locationResult.asList(),
- SingleUseCallback.wrap(onCompleteCallback));
+ @Nullable IRemoteCallback onCompleteCallback) throws RemoteException {
+ mListener.onLocationChanged(locationResult.asList(), onCompleteCallback);
}
@Override
@@ -227,7 +227,7 @@ public class LocationProviderManager extends
@Override
public void deliverOnLocationChanged(LocationResult locationResult,
- @Nullable Runnable onCompleteCallback)
+ @Nullable IRemoteCallback onCompleteCallback)
throws PendingIntent.CanceledException {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
@@ -243,20 +243,34 @@ public class LocationProviderManager extends
intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
}
+ PendingIntent.OnFinished onFinished = null;
+
// send() SHOULD only run the completion callback if it completes successfully. however,
- // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
+ // b/201299281 (which could not be fixed in the S timeframe) means that it's possible
// for send() to throw an exception AND run the completion callback. if this happens, we
// would over-release the wakelock... we take matters into our own hands to ensure that
// the completion callback can only be run if send() completes successfully. this means
// the completion callback may be run inline - but as we've never specified what thread
// the callback is run on, this is fine.
- GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
+ GatedCallback gatedCallback;
+ if (onCompleteCallback != null) {
+ gatedCallback = new GatedCallback(() -> {
+ try {
+ onCompleteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ });
+ onFinished = (pI, i, rC, rD, rE) -> gatedCallback.run();
+ } else {
+ gatedCallback = new GatedCallback(null);
+ }
mPendingIntent.send(
mContext,
0,
intent,
- (pI, i, rC, rD, rE) -> gatedCallback.run(),
+ onFinished,
null,
null,
options.toBundle());
@@ -293,7 +307,7 @@ public class LocationProviderManager extends
@Override
public void deliverOnLocationChanged(@Nullable LocationResult locationResult,
- @Nullable Runnable onCompleteCallback)
+ @Nullable IRemoteCallback onCompleteCallback)
throws RemoteException {
// ILocationCallback doesn't currently support completion callbacks
Preconditions.checkState(onCompleteCallback == null);
@@ -398,7 +412,7 @@ public class LocationProviderManager extends
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
- mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey());
+ mLocationAttributionHelper.reportLocationStart(getIdentity());
}
onHighPowerUsageChanged();
@@ -413,7 +427,7 @@ public class LocationProviderManager extends
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
- mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey());
+ mLocationAttributionHelper.reportLocationStop(getIdentity());
}
onProviderListenerInactive();
@@ -488,10 +502,10 @@ public class LocationProviderManager extends
if (!getRequest().isHiddenFromAppOps()) {
if (mIsUsingHighPower) {
mLocationAttributionHelper.reportHighPowerLocationStart(
- getIdentity(), getName(), getKey());
+ getIdentity());
} else {
mLocationAttributionHelper.reportHighPowerLocationStop(
- getIdentity(), getName(), getKey());
+ getIdentity());
}
}
}
@@ -514,8 +528,8 @@ public class LocationProviderManager extends
}
@GuardedBy("mLock")
- final boolean onLocationPermissionsChanged(String packageName) {
- if (getIdentity().getPackageName().equals(packageName)) {
+ final boolean onLocationPermissionsChanged(@Nullable String packageName) {
+ if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -714,6 +728,13 @@ public class LocationProviderManager extends
final PowerManager.WakeLock mWakeLock;
+ // b/206340085 - if we allocate a new wakelock releaser object for every delivery we
+ // increase the risk of resource starvation. if a client stops processing deliveries the
+ // system server binder allocation pool will be starved as we continue to queue up
+ // deliveries, each with a new allocation. in order to mitigate this, we use a single
+ // releaser object per registration rather than per delivery.
+ final ExternalWakeLockReleaser mWakeLockReleaser;
+
private volatile ProviderTransport mProviderTransport;
private int mNumLocationsDelivered = 0;
private long mExpirationRealtimeMs = Long.MAX_VALUE;
@@ -727,6 +748,7 @@ public class LocationProviderManager extends
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
mWakeLock.setReferenceCounted(true);
mWakeLock.setWorkSource(request.getWorkSource());
+ mWakeLockReleaser = new ExternalWakeLockReleaser(identity, mWakeLock);
}
@Override
@@ -872,6 +894,10 @@ public class LocationProviderManager extends
MAX_FASTEST_INTERVAL_JITTER_MS);
if (deltaMs
< getRequest().getMinUpdateIntervalMillis() - maxJitterMs) {
+ if (D) {
+ Log.v(TAG, mName + " provider registration " + getIdentity()
+ + " dropped delivery - too fast");
+ }
return false;
}
@@ -881,6 +907,10 @@ public class LocationProviderManager extends
if (smallestDisplacementM > 0.0 && location.distanceTo(
mPreviousLocation)
<= smallestDisplacementM) {
+ if (D) {
+ Log.v(TAG, mName + " provider registration " + getIdentity()
+ + " dropped delivery - too close");
+ }
return false;
}
}
@@ -898,7 +928,8 @@ public class LocationProviderManager extends
if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(getPermissionLevel()),
getIdentity())) {
if (D) {
- Log.w(TAG, "noteOp denied for " + getIdentity());
+ Log.w(TAG,
+ mName + " provider registration " + getIdentity() + " noteOp denied");
}
return null;
}
@@ -911,18 +942,21 @@ public class LocationProviderManager extends
@Override
public void onPreExecute() {
mUseWakeLock = false;
- final int size = locationResult.size();
- for (int i = 0; i < size; ++i) {
- if (!locationResult.get(i).isMock()) {
- mUseWakeLock = true;
- break;
+
+ // don't acquire a wakelock for passive requests or for mock locations
+ if (getRequest().getIntervalMillis() != LocationRequest.PASSIVE_INTERVAL) {
+ final int size = locationResult.size();
+ for (int i = 0; i < size; ++i) {
+ if (!locationResult.get(i).isMock()) {
+ mUseWakeLock = true;
+ break;
+ }
}
}
// update last delivered location
setLastDeliveredLocation(locationResult.getLastLocation());
- // don't acquire a wakelock for mock locations to prevent abuse
if (mUseWakeLock) {
mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
}
@@ -940,7 +974,7 @@ public class LocationProviderManager extends
}
listener.deliverOnLocationChanged(deliverLocationResult,
- mUseWakeLock ? mWakeLock::release : null);
+ mUseWakeLock ? mWakeLockReleaser : null);
EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
getIdentity());
}
@@ -1339,7 +1373,7 @@ public class LocationProviderManager extends
private final LocationPermissionsListener mLocationPermissionsListener =
new LocationPermissionsListener() {
@Override
- public void onLocationPermissionsChanged(String packageName) {
+ public void onLocationPermissionsChanged(@Nullable String packageName) {
LocationProviderManager.this.onLocationPermissionsChanged(packageName);
}
@@ -1479,7 +1513,7 @@ public class LocationProviderManager extends
public boolean isEnabled(int userId) {
if (userId == UserHandle.USER_NULL) {
return false;
- } else if (userId == UserHandle.USER_CURRENT) {
+ } else if (userId == USER_CURRENT) {
return isEnabled(mUserHelper.getCurrentUserId());
}
@@ -1652,7 +1686,7 @@ public class LocationProviderManager extends
}
}
return lastLocation;
- } else if (userId == UserHandle.USER_CURRENT) {
+ } else if (userId == USER_CURRENT) {
return getLastLocationUnsafe(mUserHelper.getCurrentUserId(), permissionLevel,
isBypass, maximumAgeMs);
}
@@ -1697,7 +1731,7 @@ public class LocationProviderManager extends
setLastLocation(location, runningUserIds[i]);
}
return;
- } else if (userId == UserHandle.USER_CURRENT) {
+ } else if (userId == USER_CURRENT) {
setLastLocation(location, mUserHelper.getCurrentUserId());
return;
}
@@ -1996,6 +2030,11 @@ public class LocationProviderManager extends
+ TimeUtils.formatDuration(delayMs));
}
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
@@ -2327,7 +2366,7 @@ public class LocationProviderManager extends
}
}
- private void onLocationPermissionsChanged(String packageName) {
+ private void onLocationPermissionsChanged(@Nullable String packageName) {
synchronized (mLock) {
updateRegistrations(
registration -> registration.onLocationPermissionsChanged(packageName));
@@ -2375,13 +2414,13 @@ public class LocationProviderManager extends
filtered = locationResult.filter(location -> {
if (!location.isMock()) {
if (location.getLatitude() == 0 && location.getLongitude() == 0) {
- Log.w(TAG, "blocking 0,0 location from " + mName + " provider");
+ Log.e(TAG, "blocking 0,0 location from " + mName + " provider");
return false;
}
}
if (!location.isComplete()) {
- Log.w(TAG, "blocking incomplete location from " + mName + " provider");
+ Log.e(TAG, "blocking incomplete location from " + mName + " provider");
return false;
}
@@ -2399,6 +2438,12 @@ public class LocationProviderManager extends
filtered = locationResult;
}
+ Location last = getLastLocationUnsafe(USER_CURRENT, PERMISSION_FINE, true, Long.MAX_VALUE);
+ if (last != null && locationResult.get(0).getElapsedRealtimeNanos()
+ < last.getElapsedRealtimeNanos()) {
+ Log.e(TAG, "non-monotonic location received from " + mName + " provider");
+ }
+
// update last location
setLastLocation(filtered.getLastLocation(), UserHandle.USER_ALL);
@@ -2753,7 +2798,7 @@ public class LocationProviderManager extends
@GuardedBy("this")
private boolean mRun;
- GatedCallback(Runnable callback) {
+ GatedCallback(@Nullable Runnable callback) {
mCallback = callback;
}
@@ -2788,4 +2833,27 @@ public class LocationProviderManager extends
}
}
}
+
+ private static class ExternalWakeLockReleaser extends IRemoteCallback.Stub {
+
+ private final CallerIdentity mIdentity;
+ private final PowerManager.WakeLock mWakeLock;
+
+ ExternalWakeLockReleaser(CallerIdentity identity, PowerManager.WakeLock wakeLock) {
+ mIdentity = identity;
+ mWakeLock = Objects.requireNonNull(wakeLock);
+ }
+
+ @Override
+ public void sendResult(Bundle data) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "wakelock over-released by " + mIdentity, e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 22a675ad39ab..5e38bca78a7c 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -23,6 +23,8 @@ import static com.android.server.location.LocationManagerService.D;
import static com.android.server.location.LocationManagerService.TAG;
import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
+import static java.lang.Math.max;
+
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
@@ -53,6 +55,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation
implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
+ private static final long MIN_INTERVAL_MS = 1000;
final Object mLock = new Object();
@@ -179,7 +182,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation
&& mLastLocation != null
&& mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
<= MAX_STATIONARY_LOCATION_AGE_MS) {
- throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
+ throttlingIntervalMs = max(mIncomingRequest.getIntervalMillis(), MIN_INTERVAL_MS);
}
ProviderRequest newRequest;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index a57d7db0ec54..1405dc45abbd 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -100,6 +100,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// State guarded by mLock.
private final Object mLock = new Object();
+
private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
private int mCurrentUserId = -1;
@@ -338,6 +339,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
+ public void setBluetoothA2dpOn(IMediaRouterClient client, boolean on) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mAudioService.setBluetoothA2dpOn(on);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn. on=" + on);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
public void setDiscoveryRequest(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
if (client == null) {
@@ -903,8 +921,26 @@ public final class MediaRouterService extends IMediaRouterService.Stub
if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
synchronized (mLock) {
+ boolean wasA2dpOn = mGlobalBluetoothA2dpOn;
mActiveBluetoothDevice = btDevice;
mGlobalBluetoothA2dpOn = btDevice != null;
+ if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
+ Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '"
+ + mGlobalBluetoothA2dpOn + "'");
+ UserRecord userRecord = mUserRecords.get(mCurrentUserId);
+ if (userRecord != null) {
+ for (ClientRecord cr : userRecord.mClientRecords) {
+ // mSelectedRouteId will be null for BT and phone speaker.
+ if (cr.mSelectedRouteId == null) {
+ try {
+ cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn);
+ } catch (RemoteException e) {
+ // Ignore exception
+ }
+ }
+ }
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index b477ea353c25..29a5469367cd 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2128,6 +2128,23 @@ public class MediaSessionService extends SystemService implements Monitor {
// Enabled notification listener only works within the same user.
return false;
}
+ // Verify whether package name and controller UID.
+ // It will indirectly check whether the caller has obtained the package name and UID
+ // via ControllerInfo or with the valid package name visibility.
+ try {
+ int actualControllerUid = mContext.getPackageManager().getPackageUidAsUser(
+ controllerPackageName,
+ UserHandle.getUserId(controllerUid));
+ if (controllerUid != actualControllerUid) {
+ Log.w(TAG, "Failed to check enabled notification listener. Package name and"
+ + " UID doesn't match");
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Failed to check enabled notification listener. Package name doesn't"
+ + " exist");
+ return false;
+ }
if (mNotificationManager.hasEnabledNotificationListener(controllerPackageName,
UserHandle.getUserHandleForUid(controllerUid))) {
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index 2519bbf389ba..8e7c4ff3e11c 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.media.metrics;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.media.MediaMetrics;
import android.media.metrics.IMediaMetricsManager;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
@@ -65,6 +66,8 @@ public final class MediaMetricsManagerService extends SystemService {
private static final int LOGGING_LEVEL_NO_UID = 1000;
private static final int LOGGING_LEVEL_BLOCKED = 99999;
+ private static final String mMetricsId = MediaMetrics.Name.METRICS_MANAGER;
+
private static final String FAILED_TO_GET = "failed_to_get";
private final SecureRandom mSecureRandom;
@GuardedBy("mLock")
@@ -199,6 +202,12 @@ public final class MediaMetricsManagerService extends SystemService {
mSecureRandom.nextBytes(byteId);
String id = Base64.encodeToString(
byteId, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
+
+ // Authorize these session ids in the native mediametrics service.
+ new MediaMetrics.Item(mMetricsId)
+ .set(MediaMetrics.Property.EVENT, "create")
+ .set(MediaMetrics.Property.LOG_SESSION_ID, id)
+ .record();
return id;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 84be7f5809e6..cfefffcdd2e8 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -4073,7 +4073,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
if (hasRestrictedModeAccess(uid)) {
uidBlockedState.allowedReasons |= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
} else {
- uidBlockedState.allowedReasons &= ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
+ uidBlockedState.allowedReasons &= ~ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
}
uidBlockedState.updateEffectiveBlockedReasons();
if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
@@ -4899,6 +4899,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0);
newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
+ newAllowedReasons |= (uidBlockedState.allowedReasons
+ & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
if (LOGV) {
Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
diff --git a/services/core/java/com/android/server/notification/BadgeExtractor.java b/services/core/java/com/android/server/notification/BadgeExtractor.java
index 70edfa171356..642cbb324229 100644
--- a/services/core/java/com/android/server/notification/BadgeExtractor.java
+++ b/services/core/java/com/android/server/notification/BadgeExtractor.java
@@ -69,11 +69,8 @@ public class BadgeExtractor implements NotificationSignalExtractor {
if (mConfig.isMediaNotificationFilteringEnabled()) {
final Notification notif = record.getNotification();
- if (notif.hasMediaSession()) {
- if (notif.isStyle(Notification.DecoratedMediaCustomViewStyle.class)
- || notif.isStyle(Notification.MediaStyle.class)) {
- record.setShowBadge(false);
- }
+ if (notif.isMediaNotification()) {
+ record.setShowBadge(false);
}
}
return null;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index ddaaa1eeff4a..7d31287663d5 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -114,9 +114,10 @@ abstract public class ManagedServices {
static final String ATT_VERSION = "version";
static final String ATT_DEFAULTS = "defaults";
static final String ATT_USER_SET = "user_set_services";
+ static final String ATT_USER_SET_OLD = "user_set";
static final String ATT_USER_CHANGED = "user_changed";
- static final int DB_VERSION = 4;
+ static final String DB_VERSION = "4";
static final int APPROVAL_BY_PACKAGE = 0;
static final int APPROVAL_BY_COMPONENT = 1;
@@ -482,7 +483,7 @@ abstract public class ManagedServices {
public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
out.startTag(null, getConfig().xmlTag);
- out.attributeInt(null, ATT_VERSION, DB_VERSION);
+ out.attributeInt(null, ATT_VERSION, Integer.parseInt(DB_VERSION));
writeDefaults(out);
@@ -615,6 +616,7 @@ abstract public class ManagedServices {
// read grants
int type;
String version = XmlUtils.readStringAttribute(parser, ATT_VERSION);
+ boolean needUpgradeUserset = false;
readDefaults(parser);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
String tag = parser.getName();
@@ -633,13 +635,42 @@ abstract public class ManagedServices {
final boolean isPrimary =
parser.getAttributeBoolean(null, ATT_IS_PRIMARY, true);
+ // Load three different userSet attributes from xml
+ // user_changed, not null if version == 4 and is NAS setting
final String isUserChanged = XmlUtils.readStringAttribute(parser,
ATT_USER_CHANGED);
- String userSetComponent = null;
- if (isUserChanged == null) {
- userSetComponent = XmlUtils.readStringAttribute(parser, ATT_USER_SET);
+ // user_set, not null if version <= 3
+ final String isUserChanged_Old = XmlUtils.readStringAttribute(parser,
+ ATT_USER_SET_OLD);
+ // user_set_services, not null if version >= 3 and is non-NAS setting
+ String userSetComponent = XmlUtils.readStringAttribute(parser, ATT_USER_SET);
+
+ // since the same xml version may have different userSet attributes,
+ // we need to check both xml version and userSet values to know how to set
+ // the userSetComponent/mIsUserChanged to the correct value
+ if (DB_VERSION.equals(version)) {
+ // version 4, NAS contains user_changed and
+ // NLS/others contain user_set_services
+ if (isUserChanged == null) { //NLS
+ userSetComponent = TextUtils.emptyIfNull(userSetComponent);
+ } else { //NAS
+ mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged));
+ userSetComponent = Boolean.valueOf(isUserChanged) ? approved : "";
+ }
} else {
- mIsUserChanged.put(resolvedUserId, Boolean.valueOf(isUserChanged));
+ // version 3 may contain user_set (R) or user_set_services (S)
+ // version 2 or older contain user_set or nothing
+ needUpgradeUserset = true;
+ if (userSetComponent == null) { //contains user_set
+ if (isUserChanged_Old != null && Boolean.valueOf(isUserChanged_Old)) {
+ //user_set = true
+ userSetComponent = approved;
+ mIsUserChanged.put(resolvedUserId, true);
+ needUpgradeUserset = false;
+ } else {
+ userSetComponent = "";
+ }
+ }
}
readExtraAttributes(tag, parser, resolvedUserId);
if (allowedManagedServicePackages == null || allowedManagedServicePackages.test(
@@ -659,7 +690,6 @@ abstract public class ManagedServices {
|| DB_VERSION_1.equals(version)
|| DB_VERSION_2.equals(version)
|| DB_VERSION_3.equals(version);
- boolean needUpgradeUserset = DB_VERSION_3.equals(version);
if (isOldVersion) {
upgradeDefaultsXmlVersion();
}
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 8aae6e09bd31..583cdd599780 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -179,7 +179,7 @@ public class NotificationComparator
}
private boolean isMediaNotification(NotificationRecord record) {
- return record.getNotification().hasMediaSession();
+ return record.getNotification().isMediaNotification();
}
private boolean isCallCategory(NotificationRecord record) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7ba0f04a435f..211f8d6e3ec7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -149,6 +149,7 @@ import android.app.NotificationHistory.HistoricalNotification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
import android.app.StatsManager;
import android.app.StatusBarManager;
import android.app.UriGrantsManager;
@@ -265,7 +266,6 @@ import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
@@ -614,12 +614,6 @@ public class NotificationManagerService extends SystemService {
private NotificationRecordLogger mNotificationRecordLogger;
private InstanceIdSequence mNotificationInstanceIdSequence;
private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
- protected static final String ACTION_ENABLE_NAS =
- "android.server.notification.action.ENABLE_NAS";
- protected static final String ACTION_DISABLE_NAS =
- "android.server.notification.action.DISABLE_NAS";
- protected static final String ACTION_LEARNMORE_NAS =
- "android.server.notification.action.LEARNMORE_NAS";
static class Archive {
final SparseArray<Boolean> mEnabled;
@@ -754,95 +748,25 @@ public class NotificationManagerService extends SystemService {
setDefaultAssistantForUser(userId);
}
- protected void migrateDefaultNASShowNotificationIfNecessary() {
+ protected void migrateDefaultNAS() {
final List<UserInfo> activeUsers = mUm.getUsers();
for (UserInfo userInfo : activeUsers) {
int userId = userInfo.getUserHandle().getIdentifier();
if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) {
continue;
}
- if (mAssistants.hasUserSet(userId)) {
- ComponentName defaultFromConfig = mAssistants.getDefaultFromConfig();
- List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
- if (allowedComponents.size() == 0) {
- setNASMigrationDone(userId);
- mAssistants.clearDefaults();
- continue;
- } else if (allowedComponents.contains(defaultFromConfig)) {
- setNASMigrationDone(userId);
- mAssistants.resetDefaultFromConfig();
- continue;
- }
- // TODO(b/192450820): re-enable when "user set" isn't over triggering
- //User selected different NAS, need onboarding
- /*enqueueNotificationInternal(getContext().getPackageName(),
- getContext().getOpPackageName(), Binder.getCallingUid(),
- Binder.getCallingPid(), TAG,
- SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE,
- createNASUpgradeNotification(userId), userId);*/
- }
- }
- }
-
- protected Notification createNASUpgradeNotification(int userId) {
- final Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- getContext().getResources().getString(R.string.global_action_settings));
- int title = R.string.nas_upgrade_notification_title;
- int content = R.string.nas_upgrade_notification_content;
-
- Intent onboardingIntent = new Intent(Settings.ACTION_NOTIFICATION_ASSISTANT_SETTINGS);
- onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
- Intent enableIntent = new Intent(ACTION_ENABLE_NAS);
- enableIntent.putExtra(Intent.EXTRA_USER_ID, userId);
- PendingIntent enableNASPendingIntent = PendingIntent.getBroadcast(getContext(),
- 0, enableIntent, PendingIntent.FLAG_IMMUTABLE);
-
- Intent disableIntent = new Intent(ACTION_DISABLE_NAS);
- disableIntent.putExtra(Intent.EXTRA_USER_ID, userId);
- PendingIntent disableNASPendingIntent = PendingIntent.getBroadcast(getContext(),
- 0, disableIntent, PendingIntent.FLAG_IMMUTABLE);
-
- Intent learnMoreIntent = new Intent(ACTION_LEARNMORE_NAS);
- learnMoreIntent.putExtra(Intent.EXTRA_USER_ID, userId);
- PendingIntent learnNASPendingIntent = PendingIntent.getBroadcast(getContext(),
- 0, learnMoreIntent, PendingIntent.FLAG_IMMUTABLE);
-
- Notification.Action enableNASAction = new Notification.Action.Builder(
- 0,
- getContext().getResources().getString(
- R.string.nas_upgrade_notification_enable_action),
- enableNASPendingIntent).build();
-
- Notification.Action disableNASAction = new Notification.Action.Builder(
- 0,
- getContext().getResources().getString(
- R.string.nas_upgrade_notification_disable_action),
- disableNASPendingIntent).build();
-
- Notification.Action learnMoreNASAction = new Notification.Action.Builder(
- 0,
- getContext().getResources().getString(
- R.string.nas_upgrade_notification_learn_more_action),
- learnNASPendingIntent).build();
-
-
- return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
- .setAutoCancel(false)
- .setOngoing(true)
- .setTicker(getContext().getResources().getString(title))
- .setSmallIcon(R.drawable.ic_settings_24dp)
- .setContentTitle(getContext().getResources().getString(title))
- .setContentText(getContext().getResources().getString(content))
- .setContentIntent(PendingIntent.getActivity(getContext(), 0, onboardingIntent,
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
- .setLocalOnly(true)
- .setStyle(new Notification.BigTextStyle())
- .addAction(enableNASAction)
- .addAction(disableNASAction)
- .addAction(learnMoreNASAction)
- .build();
+ List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
+ if (allowedComponents.size() == 0) { // user set to none
+ Slog.d(TAG, "NAS Migration: user set to none, disable new NAS setting");
+ setNASMigrationDone(userId);
+ mAssistants.clearDefaults();
+ } else {
+ Slog.d(TAG, "Reset NAS setting and migrate to new default");
+ resetAssistantUserSet(userId);
+ // migrate to new default and set migration done
+ mAssistants.resetDefaultAssistantsIfNecessary();
+ }
+ }
}
@VisibleForTesting
@@ -1256,10 +1180,11 @@ public class NotificationManagerService extends SystemService {
// Still crash for foreground services, preventing the not-crash behaviour abused
// by apps to give us a garbage notification and silently start a fg service.
Binder.withCleanCallingIdentity(
- () -> mAm.crashApplication(uid, initialPid, pkg, -1,
+ () -> mAm.crashApplicationWithType(uid, initialPid, pkg, -1,
"Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+ pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
- + message, true /* force */));
+ + message, true /* force */,
+ BadForegroundServiceNotificationException.TYPE_ID));
}
}
@@ -1859,41 +1784,6 @@ public class NotificationManagerService extends SystemService {
}
};
- private final BroadcastReceiver mNASIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
- if (ACTION_ENABLE_NAS.equals(action)) {
- mAssistants.resetDefaultFromConfig();
- setNotificationAssistantAccessGrantedForUserInternal(
- CollectionUtils.firstOrNull(mAssistants.getDefaultComponents()),
- userId, true, true);
- setNASMigrationDone(userId);
- cancelNotificationInternal(getContext().getPackageName(),
- getContext().getOpPackageName(), Binder.getCallingUid(),
- Binder.getCallingPid(), TAG,
- SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId);
- } else if (ACTION_DISABLE_NAS.equals(action)) {
- //Set default NAS to be null if user selected none during migration
- mAssistants.clearDefaults();
- setNotificationAssistantAccessGrantedForUserInternal(
- null, userId, true, true);
- setNASMigrationDone(userId);
- cancelNotificationInternal(getContext().getPackageName(),
- getContext().getOpPackageName(), Binder.getCallingUid(),
- Binder.getCallingPid(), TAG,
- SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId);
- } else if (ACTION_LEARNMORE_NAS.equals(action)) {
- Intent i = new Intent(getContext(), NASLearnMoreActivity.class);
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.of(userId));
- getContext().startActivity(i);
- }
- }
- };
-
private final class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_BADGING_URI
= Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
@@ -2405,12 +2295,6 @@ public class NotificationManagerService extends SystemService {
IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
-
- IntentFilter nasFilter = new IntentFilter();
- nasFilter.addAction(ACTION_ENABLE_NAS);
- nasFilter.addAction(ACTION_DISABLE_NAS);
- nasFilter.addAction(ACTION_LEARNMORE_NAS);
- getContext().registerReceiver(mNASIntentReceiver, nasFilter);
}
/**
@@ -2422,7 +2306,6 @@ public class NotificationManagerService extends SystemService {
getContext().unregisterReceiver(mNotificationTimeoutReceiver);
getContext().unregisterReceiver(mRestoreReceiver);
getContext().unregisterReceiver(mLocaleChangeReceiver);
- getContext().unregisterReceiver(mNASIntentReceiver);
if (mDeviceConfigChangedListener != null) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
@@ -2689,7 +2572,7 @@ public class NotificationManagerService extends SystemService {
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
registerDeviceConfigChange();
- migrateDefaultNASShowNotificationIfNecessary();
+ migrateDefaultNAS();
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
}
@@ -2803,7 +2686,7 @@ public class NotificationManagerService extends SystemService {
}
}
- private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
boolean fromApp, boolean fromListener) {
Objects.requireNonNull(group);
Objects.requireNonNull(pkg);
@@ -3851,7 +3734,8 @@ public class NotificationManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
NotificationChannelGroup groupToDelete =
- mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
+ mPreferencesHelper.getNotificationChannelGroupWithChannels(
+ pkg, callingUid, groupId, false);
if (groupToDelete != null) {
// Preflight for allowability
final int userId = UserHandle.getUserId(callingUid);
@@ -5227,10 +5111,6 @@ public class NotificationManagerService extends SystemService {
public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) {
checkCallerIsSystem();
setNASMigrationDone(userId);
- cancelNotificationInternal(getContext().getPackageName(),
- getContext().getOpPackageName(), Binder.getCallingUid(),
- Binder.getCallingPid(), TAG,
- SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId);
if (loadFromConfig) {
mAssistants.resetDefaultFromConfig();
} else {
@@ -7065,7 +6945,9 @@ public class NotificationManagerService extends SystemService {
if (index < 0) {
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
- r.setInterruptive(isVisuallyInterruptive(null, r));
+ final boolean isInterruptive = isVisuallyInterruptive(null, r);
+ r.setInterruptive(isInterruptive);
+ r.setTextChanged(isInterruptive);
} else {
old = mNotificationList.get(index); // Potentially *changes* old
mNotificationList.set(index, r);
@@ -7076,7 +6958,6 @@ public class NotificationManagerService extends SystemService {
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
- r.setInterruptive(isInterruptive);
}
mNotificationsByKey.put(n.getKey(), r);
@@ -7700,7 +7581,10 @@ public class NotificationManagerService extends SystemService {
final int waitMs = mAudioManager.getFocusRampTimeMs(
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
record.getAudioAttributes());
- if (DBG) Slog.v(TAG, "Delaying vibration by " + waitMs + "ms");
+ if (DBG) {
+ Slog.v(TAG, "Delaying vibration for notification "
+ + record.getKey() + " by " + waitMs + "ms");
+ }
try {
Thread.sleep(waitMs);
} catch (InterruptedException e) { }
@@ -7708,9 +7592,17 @@ public class NotificationManagerService extends SystemService {
// so need to check the notification still valide for vibrate.
synchronized (mNotificationLock) {
if (mNotificationsByKey.get(record.getKey()) != null) {
- vibrate(record, effect, true);
+ if (record.getKey().equals(mVibrateNotificationKey)) {
+ vibrate(record, effect, true);
+ } else {
+ if (DBG) {
+ Slog.v(TAG, "No vibration for notification "
+ + record.getKey() + ": a new notification is "
+ + "vibrating, or effects were cleared while waiting");
+ }
+ }
} else {
- Slog.e(TAG, "No vibration for canceled notification : "
+ Slog.w(TAG, "No vibration for canceled notification "
+ record.getKey());
}
}
@@ -9424,7 +9316,7 @@ public class NotificationManagerService extends SystemService {
record.getSystemGeneratedSmartActions(),
record.getSmartReplies(),
record.canBubble(),
- record.isInterruptive(),
+ record.isTextChanged(),
record.isConversation(),
record.getShortcutInfo(),
record.getRankingScore() == 0
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b4ca5118e10f..b6b54fc19011 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1143,6 +1143,10 @@ public final class NotificationRecord {
return mIsInterruptive;
}
+ public boolean isTextChanged() {
+ return mTextChanged;
+ }
+
/** Returns the time the notification audibly alerted the user. */
public long getLastAudiblyAlertedMs() {
return mLastAudiblyAlertedMs;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index e8a3a8150377..185c0231b534 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -796,6 +796,9 @@ public class PreferencesHelper implements RankingConfig {
if (r == null) {
throw new IllegalArgumentException("Invalid package");
}
+ if (fromTargetApp) {
+ group.setBlocked(false);
+ }
final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
if (oldGroup != null) {
group.setChannels(oldGroup.getChannels());
diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java
index f47aa487744a..449fae13f137 100644
--- a/services/core/java/com/android/server/notification/VibratorHelper.java
+++ b/services/core/java/com/android/server/notification/VibratorHelper.java
@@ -19,6 +19,7 @@ package com.android.server.notification;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.media.AudioAttributes;
import android.os.Process;
import android.os.VibrationAttributes;
@@ -43,11 +44,12 @@ public final class VibratorHelper {
private final Vibrator mVibrator;
private final long[] mDefaultPattern;
private final long[] mFallbackPattern;
+ @Nullable private final float[] mDefaultPwlePattern;
+ @Nullable private final float[] mFallbackPwlePattern;
public VibratorHelper(Context context) {
mVibrator = context.getSystemService(Vibrator.class);
- mDefaultPattern = getLongArray(
- context.getResources(),
+ mDefaultPattern = getLongArray(context.getResources(),
com.android.internal.R.array.config_defaultNotificationVibePattern,
VIBRATE_PATTERN_MAXLEN,
DEFAULT_VIBRATE_PATTERN);
@@ -55,6 +57,10 @@ public final class VibratorHelper {
R.array.config_notificationFallbackVibePattern,
VIBRATE_PATTERN_MAXLEN,
DEFAULT_VIBRATE_PATTERN);
+ mDefaultPwlePattern = getFloatArray(context.getResources(),
+ com.android.internal.R.array.config_defaultNotificationVibeWaveform);
+ mFallbackPwlePattern = getFloatArray(context.getResources(),
+ com.android.internal.R.array.config_notificationFallbackVibeWaveform);
}
/**
@@ -80,6 +86,50 @@ public final class VibratorHelper {
}
/**
+ * Safely create a {@link VibrationEffect} from given waveform description.
+ *
+ * <p>The waveform is described by a sequence of values for target amplitude, frequency and
+ * duration, that are forwarded to
+ * {@link VibrationEffect.WaveformBuilder#addRamp(float, float, int)}.
+ *
+ * <p>This method returns {@code null} if the pattern is also {@code null} or invalid.
+ *
+ * @param values The list of values describing the waveform as a sequence of target amplitude,
+ * frequency and duration.
+ * @param insistent {@code true} if the vibration should loop until it is cancelled.
+ */
+ @Nullable
+ public static VibrationEffect createPwleWaveformVibration(@Nullable float[] values,
+ boolean insistent) {
+ try {
+ if (values == null) {
+ return null;
+ }
+
+ int length = values.length;
+ // The waveform is described by triples (amplitude, frequency, duration)
+ if ((length == 0) || (length % 3 != 0)) {
+ return null;
+ }
+
+ VibrationEffect.WaveformBuilder waveformBuilder = VibrationEffect.startWaveform();
+ for (int i = 0; i < length; i += 3) {
+ waveformBuilder.addRamp(/* amplitude= */ values[i], /* frequency= */ values[i + 1],
+ /* duration= */ (int) values[i + 2]);
+ }
+
+ if (insistent) {
+ return waveformBuilder.build(/* repeat= */ 0);
+ }
+ return waveformBuilder.build();
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Error creating vibration PWLE waveform with pattern: "
+ + Arrays.toString(values));
+ }
+ return null;
+ }
+
+ /**
* Vibrate the device with given {@code effect}.
*
* <p>We need to vibrate as "android" so we can breakthrough DND.
@@ -102,6 +152,12 @@ public final class VibratorHelper {
* @param insistent {@code true} if the vibration should loop until it is cancelled.
*/
public VibrationEffect createFallbackVibration(boolean insistent) {
+ if (mVibrator.hasFrequencyControl()) {
+ VibrationEffect effect = createPwleWaveformVibration(mFallbackPwlePattern, insistent);
+ if (effect != null) {
+ return effect;
+ }
+ }
return createWaveformVibration(mFallbackPattern, insistent);
}
@@ -111,9 +167,32 @@ public final class VibratorHelper {
* @param insistent {@code true} if the vibration should loop until it is cancelled.
*/
public VibrationEffect createDefaultVibration(boolean insistent) {
+ if (mVibrator.hasFrequencyControl()) {
+ VibrationEffect effect = createPwleWaveformVibration(mDefaultPwlePattern, insistent);
+ if (effect != null) {
+ return effect;
+ }
+ }
return createWaveformVibration(mDefaultPattern, insistent);
}
+ @Nullable
+ private static float[] getFloatArray(Resources resources, int resId) {
+ TypedArray array = resources.obtainTypedArray(resId);
+ try {
+ float[] values = new float[array.length()];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = array.getFloat(i, Float.NaN);
+ if (Float.isNaN(values[i])) {
+ return null;
+ }
+ }
+ return values;
+ } finally {
+ array.recycle();
+ }
+ }
+
private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) {
int[] ar = resources.getIntArray(resId);
if (ar == null) {
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index ed1f5f567d95..3fc416931a06 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -356,7 +356,7 @@ public final class NativeTombstoneManager {
return false;
}
- if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 1000) {
+ if (Math.abs(exitInfo.getTimestamp() - mTimestampMs) > 5000) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index d6400f3c879e..b500c9930e00 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -381,7 +381,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
}
// Does the package have code? If not, there won't be any artifacts.
- if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
+ if (!mPackageManagerService.mPackageDexOptimizer.canOptimizePackage(pkg)) {
continue;
}
if (pkg.getPath() == null) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 44f7d8869322..040457bf9000 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -63,7 +63,10 @@ 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.IndentingPrintWriter;
+import com.android.server.LocalServices;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.ArtStatsLogUtils;
@@ -107,16 +110,24 @@ public class PackageDexOptimizer {
private volatile boolean mSystemReady;
private final ArtStatsLogger mArtStatsLogger = new ArtStatsLogger();
+ private final Injector mInjector;
+
private static final Random sRandom = new Random();
PackageDexOptimizer(Installer installer, Object installLock, Context context,
String wakeLockTag) {
- this.mInstaller = installer;
- this.mInstallLock = installLock;
+ this(new Injector() {
+ @Override
+ public AppHibernationManagerInternal getAppHibernationManagerInternal() {
+ return LocalServices.getService(AppHibernationManagerInternal.class);
+ }
- PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+ @Override
+ public PowerManager getPowerManager(Context context) {
+ return context.getSystemService(PowerManager.class);
+ }
+ }, installer, installLock, context, wakeLockTag);
}
protected PackageDexOptimizer(PackageDexOptimizer from) {
@@ -124,9 +135,21 @@ public class PackageDexOptimizer {
this.mInstallLock = from.mInstallLock;
this.mDexoptWakeLock = from.mDexoptWakeLock;
this.mSystemReady = from.mSystemReady;
+ this.mInjector = from.mInjector;
+ }
+
+ @VisibleForTesting
+ PackageDexOptimizer(@NonNull Injector injector, Installer installer, Object installLock,
+ Context context, String wakeLockTag) {
+ this.mInstaller = installer;
+ this.mInstallLock = installLock;
+
+ PowerManager powerManager = injector.getPowerManager(context);
+ mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+ mInjector = injector;
}
- static boolean canOptimizePackage(AndroidPackage pkg) {
+ boolean canOptimizePackage(AndroidPackage pkg) {
// We do not dexopt a package with no code.
// Note that the system package is marked as having no code, however we can
// still optimize it via dexoptSystemServerPath.
@@ -134,6 +157,17 @@ public class PackageDexOptimizer {
return false;
}
+ // We do not dexopt unused packages.
+ // It's possible for this to be called before app hibernation service is ready due to
+ // an OTA dexopt. In this case, we ignore the hibernation check here. This is fine since
+ // a hibernating app should have no artifacts to copy in the first place.
+ AppHibernationManagerInternal ahm = mInjector.getAppHibernationManagerInternal();
+ if (ahm != null
+ && ahm.isHibernatingGlobally(pkg.getPackageName())
+ && ahm.isOatArtifactDeletionEnabled()) {
+ return false;
+ }
+
return true;
}
@@ -921,4 +955,13 @@ public class PackageDexOptimizer {
return flags | DEXOPT_FORCE;
}
}
+
+ /**
+ * Injector for {@link PackageDexOptimizer} dependencies
+ */
+ interface Injector {
+ AppHibernationManagerInternal getAppHibernationManagerInternal();
+
+ PowerManager getPowerManager(Context context);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index f317d311c87a..d07907671703 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -388,7 +388,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) {
// Aggressively close old sessions because we are running low on storage
// Their staging dirs will be removed too
- session.abandon();
+ PackageInstallerSession root = !session.hasParentSessionId()
+ ? session : mSessions.get(session.getParentSessionId());
+ if (!root.isDestroyed()) {
+ root.abandon();
+ }
} else {
// Session is new enough, so it deserves to be kept even on low storage
unclaimedStagingDirsOnVolume.remove(session.stageDir);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1d14bb59c56a..70ecaa7a458c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -368,7 +368,6 @@ import com.android.server.SystemConfig;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
-import com.android.server.apphibernation.AppHibernationService;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -1471,7 +1470,7 @@ public class PackageManagerService extends IPackageManager.Stub
final ArtManagerService mArtManagerService;
- private final PackageDexOptimizer mPackageDexOptimizer;
+ final PackageDexOptimizer mPackageDexOptimizer;
// DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
// is used by other apps).
private final DexManager mDexManager;
@@ -4807,7 +4806,7 @@ public class PackageManagerService extends IPackageManager.Stub
try {
mDomainVerificationManager.printState(writer, packageName,
UserHandle.USER_ALL, mSettings::getPackageLPr);
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (Exception e) {
pw.println("Failure printing domain verification information");
Slog.e(TAG, "Failure printing domain verification information", e);
}
@@ -10695,7 +10694,7 @@ public class PackageManagerService extends IPackageManager.Stub
userId);
// Find any earlier preferred or last chosen entries and nuke them
findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, false, true, false, userId);
+ intent, resolvedType, flags, query, 0, false, true, false, userId);
// Add the new activity as the last chosen for this filter
addPreferredActivity(filter, match, null, activity, false, userId,
"Setting last chosen", false);
@@ -10711,7 +10710,7 @@ public class PackageManagerService extends IPackageManager.Stub
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, false, false, false, userId);
+ intent, resolvedType, flags, query, 0, false, false, false, userId);
}
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -10753,7 +10752,7 @@ public class PackageManagerService extends IPackageManager.Stub
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
- flags, query, true, false, debug, userId, queryMayBeFiltered);
+ flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}
@@ -10899,17 +10898,17 @@ public class PackageManagerService extends IPackageManager.Stub
}
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, boolean always,
+ List<ResolveInfo> query, int priority, boolean always,
boolean removeMatches, boolean debug, int userId) {
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, always, removeMatches, debug, userId,
+ intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId,
UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
}
// TODO: handle preferred activities missing while user has amnesia
/** <b>must not hold {@link #mLock}</b> */
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, boolean always,
+ List<ResolveInfo> query, int priority, boolean always,
boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
if (Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
@@ -12866,7 +12865,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
- if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
+ if (!mPackageDexOptimizer.canOptimizePackage(pkg)) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
}
@@ -12947,7 +12946,7 @@ public class PackageManagerService extends IPackageManager.Stub
return;
}
} else {
- if (isInstantApp(packageName, callingUserId)) {
+ if (isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID)) {
return;
}
}
@@ -13117,16 +13116,11 @@ public class PackageManagerService extends IPackageManager.Stub
ArraySet<String> pkgs = new ArraySet<>();
synchronized (mLock) {
for (AndroidPackage p : mPackages.values()) {
- if (PackageDexOptimizer.canOptimizePackage(p)) {
+ if (mPackageDexOptimizer.canOptimizePackage(p)) {
pkgs.add(p.getPackageName());
}
}
}
- if (AppHibernationService.isAppHibernationEnabled()) {
- AppHibernationManagerInternal appHibernationManager =
- mInjector.getLocalService(AppHibernationManagerInternal.class);
- pkgs.removeIf(pkgName -> appHibernationManager.isHibernatingGlobally(pkgName));
- }
return pkgs;
}
@@ -15486,7 +15480,8 @@ public class PackageManagerService extends IPackageManager.Stub
mResolveActivity.processName = "system:ui";
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
- mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+ mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
+ | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
@@ -23776,7 +23771,7 @@ public class PackageManagerService extends IPackageManager.Stub
final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
- intent, null, 0, resolveInfos, true, false, false, userId);
+ intent, null, 0, resolveInfos, 0, true, false, false, userId);
final String packageName = preferredResolveInfo != null
&& preferredResolveInfo.activityInfo != null
? preferredResolveInfo.activityInfo.packageName : null;
@@ -24477,24 +24472,24 @@ public class PackageManagerService extends IPackageManager.Stub
}
enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
true /* checkShell */, "stop package");
- boolean shouldUnhibernate = false;
// writer
synchronized (mLock) {
final PackageSetting ps = mSettings.getPackageLPr(packageName);
- if (ps != null && ps.getStopped(userId) && !stopped) {
- shouldUnhibernate = true;
- }
if (!shouldFilterApplicationLocked(ps, callingUid, userId)
&& mSettings.setPackageStoppedStateLPw(this, packageName, stopped, userId)) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
- if (shouldUnhibernate) {
+ // If this would cause the app to leave force-stop, then also make sure to unhibernate the
+ // app if needed.
+ if (!stopped) {
mHandler.post(() -> {
AppHibernationManagerInternal ah =
mInjector.getLocalService(AppHibernationManagerInternal.class);
- ah.setHibernatingForUser(packageName, userId, false);
- ah.setHibernatingGlobally(packageName, false);
+ if (ah != null && ah.isHibernatingForUser(packageName, userId)) {
+ ah.setHibernatingForUser(packageName, userId, false);
+ ah.setHibernatingGlobally(packageName, false);
+ }
});
}
}
@@ -29007,8 +29002,12 @@ public class PackageManagerService extends IPackageManager.Stub
@Override
public void setSplashScreenTheme(@NonNull String packageName, @Nullable String themeId,
int userId) {
- int callingUid = Binder.getCallingUid();
- PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid, userId);
+ final int callingUid = Binder.getCallingUid();
+ enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,
+ false /* checkShell */, "setSplashScreenTheme");
+ enforceOwnerRights(packageName, callingUid);
+ final PackageSetting packageSetting = getPackageSettingForUser(packageName, callingUid,
+ userId);
if (packageSetting != null) {
packageSetting.setSplashScreenTheme(userId, themeId);
}
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 c2c5d91eafa8..cb28637e8c10 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -914,6 +914,11 @@ final class DefaultPermissionGrantPolicy {
}
grantPermissionsToSystemPackage(pm, dialerPackage, userId,
CONTACTS_PERMISSIONS, SMS_PERMISSIONS, MICROPHONE_PERMISSIONS, CAMERA_PERMISSIONS);
+ boolean isAndroidAutomotive =
+ mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
+ if (isAndroidAutomotive) {
+ grantPermissionsToSystemPackage(pm, dialerPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
}
private void grantDefaultPermissionsToDefaultSystemSmsApp(PackageManagerWrapper pm,
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index 0b48b5c6dd70..2208e3c02c4d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -70,8 +70,11 @@ public class DomainVerificationEnforcer {
break;
default:
if (!proxy.isCallerVerifier(callingUid)) {
- throw new SecurityException(
- "Caller is not allowed to query domain verification state");
+ mContext.enforcePermission(android.Manifest.permission.DUMP,
+ Binder.getCallingPid(), callingUid,
+ "Caller " + callingUid
+ + " is not allowed to query domain verification state");
+ break;
}
mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index ba64d25178e7..2270df375245 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -896,7 +896,7 @@ public class DomainVerificationService extends SystemService
oldPkgState.getUserStates();
int oldUserStatesSize = oldUserStates.size();
if (oldUserStatesSize > 0) {
- ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg);
+ ArraySet<String> newWebDomains = mCollector.collectAllWebDomains(newPkg);
for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize;
oldUserStatesIndex++) {
int userId = oldUserStates.keyAt(oldUserStatesIndex);
@@ -1193,6 +1193,7 @@ public class DomainVerificationService extends SystemService
@Nullable @UserIdInt Integer userId,
@NonNull Function<String, PackageSetting> pkgSettingFunction)
throws NameNotFoundException {
+ mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
synchronized (mLock) {
mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index edd5f5f415c6..27a16e9bfdda 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -41,6 +41,7 @@ import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
import com.android.server.policy.devicestate.config.Conditions;
import com.android.server.policy.devicestate.config.DeviceStateConfig;
+import com.android.server.policy.devicestate.config.Flags;
import com.android.server.policy.devicestate.config.LidSwitchCondition;
import com.android.server.policy.devicestate.config.NumericRange;
import com.android.server.policy.devicestate.config.SensorCondition;
@@ -81,13 +82,14 @@ import javax.xml.datatype.DatatypeConfigurationException;
public final class DeviceStateProviderImpl implements DeviceStateProvider,
InputManagerInternal.LidSwitchCallback, SensorEventListener {
private static final String TAG = "DeviceStateProviderImpl";
+ private static final boolean DEBUG = false;
private static final BooleanSupplier TRUE_BOOLEAN_SUPPLIER = () -> true;
private static final BooleanSupplier FALSE_BOOLEAN_SUPPLIER = () -> false;
@VisibleForTesting
static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(MINIMUM_DEVICE_STATE,
- "DEFAULT");
+ "DEFAULT", 0 /* flags */);
private static final String VENDOR_CONFIG_FILE_PATH = "etc/devicestate/";
private static final String DATA_CONFIG_FILE_PATH = "system/devicestate/";
@@ -131,7 +133,26 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
config.getDeviceState()) {
final int state = stateConfig.getIdentifier().intValue();
final String name = stateConfig.getName() == null ? "" : stateConfig.getName();
- deviceStateList.add(new DeviceState(state, name));
+
+ int flags = 0;
+ final Flags configFlags = stateConfig.getFlags();
+ if (configFlags != null) {
+ List<String> configFlagStrings = configFlags.getFlag();
+ for (int i = 0; i < configFlagStrings.size(); i++) {
+ final String configFlagString = configFlagStrings.get(i);
+ switch (configFlagString) {
+ case "FLAG_CANCEL_STICKY_REQUESTS":
+ flags |= DeviceState.FLAG_CANCEL_STICKY_REQUESTS;
+ break;
+ default:
+ Slog.w(TAG, "Parsed unknown flag with name: "
+ + configFlagString);
+ break;
+ }
+ }
+ }
+
+ deviceStateList.add(new DeviceState(state, name, flags));
final Conditions condition = stateConfig.getConditions();
conditionsList.add(condition);
@@ -193,6 +214,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
for (int i = 0; i < stateConditions.size(); i++) {
final int state = deviceStates.get(i).getIdentifier();
+ if (DEBUG) {
+ Slog.d(TAG, "Evaluating conditions for device state " + state
+ + " (" + deviceStates.get(i).getName() + ")");
+ }
final Conditions conditions = stateConditions.get(i);
if (conditions == null) {
mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
@@ -213,6 +238,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
if (lidSwitchCondition != null) {
suppliers.add(new LidSwitchBooleanSupplier(lidSwitchCondition.getOpen()));
lidSwitchRequired = true;
+ if (DEBUG) {
+ Slog.d(TAG, "Lid switch required");
+ }
}
List<SensorCondition> sensorConditions = conditions.getSensor();
@@ -229,6 +257,11 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
break;
}
+ if (DEBUG) {
+ Slog.d(TAG, "Found sensor with type: " + expectedSensorType
+ + " (" + expectedSensorName + ")");
+ }
+
suppliers.add(new SensorBooleanSupplier(foundSensor, sensorCondition.getValue()));
sensorsRequired.add(foundSensor);
}
@@ -323,6 +356,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
int newState = mOrderedStates[0].getIdentifier();
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
+ if (DEBUG) {
+ Slog.d(TAG, "Checking conditions for " + mOrderedStates[i].getName() + "("
+ + i + ")");
+ }
boolean conditionSatisfied;
try {
conditionSatisfied = mStateConditions.get(state).getAsBoolean();
@@ -330,10 +367,16 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
// Failed to compute the current state based on current available data. Return
// with the expectation that notifyDeviceStateChangedIfNeeded() will be called
// when a callback with the missing data is triggered.
+ if (DEBUG) {
+ Slog.d(TAG, "Unable to check current state", e);
+ }
return;
}
if (conditionSatisfied) {
+ if (DEBUG) {
+ Slog.d(TAG, "Device State conditions satisfied, transition to " + state);
+ }
newState = state;
break;
}
@@ -355,6 +398,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
synchronized (mLock) {
mIsLidOpen = lidOpen;
}
+ if (DEBUG) {
+ Slog.d(TAG, "Lid switch state: " + (lidOpen ? "open" : "closed"));
+ }
notifyDeviceStateChangedIfNeeded();
}
@@ -440,6 +486,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
private boolean adheresToRange(float value, @NonNull NumericRange range) {
final BigDecimal min = range.getMin_optional();
if (min != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "value: " + value + ", constraint min: " + min.floatValue());
+ }
if (value <= min.floatValue()) {
return false;
}
@@ -447,6 +496,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
final BigDecimal minInclusive = range.getMinInclusive_optional();
if (minInclusive != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "value: " + value + ", constraint min-inclusive: "
+ + minInclusive.floatValue());
+ }
if (value < minInclusive.floatValue()) {
return false;
}
@@ -454,6 +507,9 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
final BigDecimal max = range.getMax_optional();
if (max != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "value: " + value + ", constraint max: " + max.floatValue());
+ }
if (value >= max.floatValue()) {
return false;
}
@@ -461,6 +517,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
final BigDecimal maxInclusive = range.getMaxInclusive_optional();
if (maxInclusive != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "value: " + value + ", constraint max-inclusive: "
+ + maxInclusive.floatValue());
+ }
if (value > maxInclusive.floatValue()) {
return false;
}
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index 3c9b1063ba93..04b5005aa283 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -16,10 +16,8 @@
package com.android.server.policy;
-import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
-import android.hardware.ICameraService;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManagerInternal;
@@ -27,13 +25,11 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.IDisplayFoldListener;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
-import com.android.server.camera.CameraServiceProxy;
import com.android.server.wm.WindowManagerInternal;
/**
@@ -41,13 +37,8 @@ import com.android.server.wm.WindowManagerInternal;
* TODO(b/126160895): Move DisplayFoldController from PhoneWindowManager to DisplayPolicy.
*/
class DisplayFoldController {
- private static final String TAG = "DisplayFoldController";
-
private final WindowManagerInternal mWindowManagerInternal;
private final DisplayManagerInternal mDisplayManagerInternal;
- // Camera service proxy can be disabled through a config.
- @Nullable
- private final CameraServiceProxy mCameraServiceProxy;
private final int mDisplayId;
private final Handler mHandler;
@@ -64,12 +55,10 @@ class DisplayFoldController {
DisplayFoldController(
Context context, WindowManagerInternal windowManagerInternal,
- DisplayManagerInternal displayManagerInternal,
- @Nullable CameraServiceProxy cameraServiceProxy, int displayId, Rect foldedArea,
+ DisplayManagerInternal displayManagerInternal, int displayId, Rect foldedArea,
Handler handler) {
mWindowManagerInternal = windowManagerInternal;
mDisplayManagerInternal = displayManagerInternal;
- mCameraServiceProxy = cameraServiceProxy;
mDisplayId = displayId;
mFoldedArea = new Rect(foldedArea);
mHandler = handler;
@@ -124,16 +113,6 @@ class DisplayFoldController {
}
}
- if (mCameraServiceProxy != null) {
- if (folded) {
- mCameraServiceProxy.setDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
- } else {
- mCameraServiceProxy.clearDeviceStateFlags(ICameraService.DEVICE_STATE_FOLDED);
- }
- } else {
- Slog.w(TAG, "Camera service unavailable to toggle folded state.");
- }
-
mDurationLogger.setDeviceFolded(folded);
mDurationLogger.logFocusedAppWithFoldState(folded, mFocusedApp);
mFolded = folded;
@@ -188,8 +167,6 @@ class DisplayFoldController {
LocalServices.getService(WindowManagerInternal.class);
final DisplayManagerInternal displayService =
LocalServices.getService(DisplayManagerInternal.class);
- final CameraServiceProxy cameraServiceProxy =
- LocalServices.getService(CameraServiceProxy.class);
final String configFoldedArea = context.getResources().getString(
com.android.internal.R.string.config_foldedArea);
@@ -201,6 +178,6 @@ class DisplayFoldController {
}
return new DisplayFoldController(context, windowManagerService, displayService,
- cameraServiceProxy, displayId, foldedArea, DisplayThread.getHandler());
+ displayId, foldedArea, DisplayThread.getHandler());
}
}
diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java
index 268de3e2182b..68e078c519ba 100644
--- a/services/core/java/com/android/server/policy/KeyCombinationManager.java
+++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java
@@ -17,11 +17,13 @@ package com.android.server.policy;
import static android.view.KeyEvent.KEYCODE_POWER;
+import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseLongArray;
import android.view.KeyEvent;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ToBooleanFunction;
import java.io.PrintWriter;
@@ -35,13 +37,18 @@ public class KeyCombinationManager {
private static final String TAG = "KeyCombinationManager";
// Store the received down time of keycode.
+ @GuardedBy("mLock")
private final SparseLongArray mDownTimes = new SparseLongArray(2);
private final ArrayList<TwoKeysCombinationRule> mRules = new ArrayList();
// Selected rules according to current key down.
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private final ArrayList<TwoKeysCombinationRule> mActiveRules = new ArrayList();
// The rule has been triggered by current keys.
+ @GuardedBy("mLock")
private TwoKeysCombinationRule mTriggeredRule;
+ private final Handler mHandler = new Handler();
// Keys in a key combination must be pressed within this interval of each other.
private static final long COMBINE_KEY_DELAY_MILLIS = 150;
@@ -109,6 +116,12 @@ public class KeyCombinationManager {
* Return true if any active rule could be triggered by the key event, otherwise false.
*/
boolean interceptKey(KeyEvent event, boolean interactive) {
+ synchronized (mLock) {
+ return interceptKeyLocked(event, interactive);
+ }
+ }
+
+ private boolean interceptKeyLocked(KeyEvent event, boolean interactive) {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final int keyCode = event.getKeyCode();
final int count = mActiveRules.size();
@@ -154,7 +167,7 @@ public class KeyCombinationManager {
return false;
}
Log.v(TAG, "Performing combination rule : " + rule);
- rule.execute();
+ mHandler.post(rule::execute);
mTriggeredRule = rule;
return true;
});
@@ -169,7 +182,7 @@ public class KeyCombinationManager {
for (int index = count - 1; index >= 0; index--) {
final TwoKeysCombinationRule rule = mActiveRules.get(index);
if (rule.shouldInterceptKey(keyCode)) {
- rule.cancel();
+ mHandler.post(rule::cancel);
mActiveRules.remove(index);
}
}
@@ -181,31 +194,37 @@ public class KeyCombinationManager {
* Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window.
*/
long getKeyInterceptTimeout(int keyCode) {
- if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) {
- return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS;
+ synchronized (mLock) {
+ if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) {
+ return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS;
+ }
+ return 0;
}
- return 0;
}
/**
* True if the key event had been handled.
*/
boolean isKeyConsumed(KeyEvent event) {
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
- return false;
+ synchronized (mLock) {
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+ return false;
+ }
+ return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode());
}
- return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode());
}
/**
* True if power key is the candidate.
*/
boolean isPowerKeyIntercepted() {
- if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) {
- // return false if only if power key pressed.
- return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0;
+ synchronized (mLock) {
+ if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) {
+ // return false if only if power key pressed.
+ return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0;
+ }
+ return false;
}
- return false;
}
/**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 138d55e09742..b02d08027510 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,7 +47,6 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
-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.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
@@ -405,7 +404,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private AccessibilityShortcutController mAccessibilityShortcutController;
boolean mSafeMode;
- private WindowState mKeyguardCandidate = null;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
// This is for car dock and this is updated from resource.
@@ -508,7 +506,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private boolean mKeyguardOccludedChanged;
private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
- volatile boolean mKeyguardOccluded;
Intent mHomeIntent;
Intent mCarDockIntent;
Intent mDeskDockIntent;
@@ -1831,14 +1828,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
- return handleStartTransitionForKeyguardLw(keyguardGoingAway, duration);
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
+ // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+ // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+ // need to call IKeyguardService#keyguardGoingAway here.
+ return handleStartTransitionForKeyguardLw(keyguardGoingAway
+ && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation,
+ keyguardOccluding, duration);
}
@Override
public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
- handleStartTransitionForKeyguardLw(keyguardGoingAway, 0 /* duration */);
+ handleStartTransitionForKeyguardLw(
+ keyguardGoingAway, false /* keyguardOccludingStarted */,
+ 0 /* duration */);
}
});
@@ -2415,7 +2420,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// the keyguard is being hidden. This is okay because starting windows never show
// secret information.
// TODO(b/113840485): Occluded may not only happen on default display
- if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) {
+ if (displayId == DEFAULT_DISPLAY && isKeyguardOccluded()) {
windowFlags |= FLAG_SHOW_WHEN_LOCKED;
}
}
@@ -3056,31 +3061,34 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public void onKeyguardOccludedChangedLw(boolean occluded) {
- if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
+ if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()
+ && !WindowManagerService.sEnableShellTransitions) {
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
- setKeyguardOccludedLw(occluded, false /* force */);
+ setKeyguardOccludedLw(occluded, false /* force */,
+ false /* transitionStarted */);
}
}
@Override
- public int applyKeyguardOcclusionChange() {
+ public int applyKeyguardOcclusionChange(boolean transitionStarted) {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
- mKeyguardOccludedChanged = false;
- if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */)) {
+ if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */,
+ transitionStarted)) {
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
}
}
return 0;
}
- private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) {
- final int res = applyKeyguardOcclusionChange();
- if (res != 0) return res;
- if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation && keyguardGoingAway) {
+ private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration) {
+ final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding);
+ if (redoLayout != 0) return redoLayout;
+ if (keyguardGoingAway) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
}
@@ -3230,7 +3238,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return;
}
- if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) {
+ if (!isKeyguardOccluded() && mKeyguardDelegate.isInputRestricted()) {
// when in keyguard restricted mode, must first verify unlock
// before launching home
mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
@@ -3277,46 +3285,32 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mNavBarVirtualKeyHapticFeedbackEnabled = enabled;
}
- /** {@inheritDoc} */
- @Override
- public void setKeyguardCandidateLw(WindowState win) {
- mKeyguardCandidate = win;
- setKeyguardOccludedLw(mKeyguardOccluded, true /* force */);
- }
-
/**
* Updates the occluded state of the Keyguard.
*
+ * @param isOccluded Whether the Keyguard is occluded by another window.
+ * @param force notify the occluded status to KeyguardService and update flags even though
+ * occlude status doesn't change.
+ * @param transitionStarted {@code true} if keyguard (un)occluded transition started.
* @return Whether the flags have changed and we have to redo the layout.
*/
- private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force) {
+ private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
+ boolean transitionStarted) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
- final boolean wasOccluded = mKeyguardOccluded;
- final boolean showing = mKeyguardDelegate.isShowing();
- final boolean changed = wasOccluded != isOccluded || force;
- if (!isOccluded && changed && showing) {
- mKeyguardOccluded = false;
- mKeyguardDelegate.setOccluded(false, true /* animate */);
- if (mKeyguardCandidate != null) {
- if (!mKeyguardDelegate.hasLockscreenWallpaper()) {
- mKeyguardCandidate.getAttrs().flags |= FLAG_SHOW_WALLPAPER;
- }
- }
- return true;
- } else if (isOccluded && changed && showing) {
- mKeyguardOccluded = true;
- mKeyguardDelegate.setOccluded(true, false /* animate */);
- if (mKeyguardCandidate != null) {
- mKeyguardCandidate.getAttrs().flags &= ~FLAG_SHOW_WALLPAPER;
- }
- return true;
- } else if (changed) {
- mKeyguardOccluded = isOccluded;
- mKeyguardDelegate.setOccluded(isOccluded, false /* animate */);
- return false;
- } else {
+ mKeyguardOccludedChanged = false;
+ if (isKeyguardOccluded() == isOccluded && !force) {
return false;
}
+
+ final boolean showing = mKeyguardDelegate.isShowing();
+ final boolean animate = showing && !isOccluded;
+ // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService
+ // uses remote animation start as a signal to update its occlusion status ,so we don't need
+ // to notify here.
+ final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
+ || !transitionStarted;
+ mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
+ return showing;
}
/** {@inheritDoc} */
@@ -4288,6 +4282,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pmWakeReason)) + ")");
}
+ mActivityTaskManagerInternal.notifyWakingUp();
mDefaultDisplayPolicy.setAwake(true);
// Since goToSleep performs these functions synchronously, we must
@@ -4612,7 +4607,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public boolean isKeyguardShowingAndNotOccluded() {
if (mKeyguardDelegate == null) return false;
- return mKeyguardDelegate.isShowing() && !mKeyguardOccluded;
+ return mKeyguardDelegate.isShowing() && !isKeyguardOccluded();
}
@Override
@@ -4638,7 +4633,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public boolean isKeyguardOccluded() {
if (mKeyguardDelegate == null) return false;
- return mKeyguardOccluded;
+ return mKeyguardDelegate.isOccluded();
}
/** {@inheritDoc} */
@@ -5330,7 +5325,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
proto.write(KEYGUARD_DRAW_COMPLETE, mDefaultDisplayPolicy.isKeyguardDrawComplete());
proto.write(WINDOW_MANAGER_DRAW_COMPLETE,
mDefaultDisplayPolicy.isWindowManagerDrawComplete());
- proto.write(KEYGUARD_OCCLUDED, mKeyguardOccluded);
+ proto.write(KEYGUARD_OCCLUDED, isKeyguardOccluded());
proto.write(KEYGUARD_OCCLUDED_CHANGED, mKeyguardOccludedChanged);
proto.write(KEYGUARD_OCCLUDED_PENDING, mPendingKeyguardOccluded);
if (mKeyguardDelegate != null) {
@@ -5415,7 +5410,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final int key = mDisplayHomeButtonHandlers.keyAt(i);
pw.println(mDisplayHomeButtonHandlers.get(key));
}
- pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(mKeyguardOccluded);
+ pw.print(prefix); pw.print("mKeyguardOccluded="); pw.print(isKeyguardOccluded());
pw.print(" mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
pw.print(" mPendingKeyguardOccluded="); pw.println(mPendingKeyguardOccluded);
pw.print(prefix); pw.print("mAllowLockscreenWhenOnDisplays=");
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 510ab93e1af5..2f2f94d3b5de 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -173,8 +173,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
*/
void onKeyguardOccludedChangedLw(boolean occluded);
- /** Applies a keyguard occlusion change if one happened. */
- int applyKeyguardOcclusionChange();
+ /**
+ * Applies a keyguard occlusion change if one happened.
+ * @param transitionStarted Whether keyguard (un)occlude transition is starting or not.
+ */
+ int applyKeyguardOcclusionChange(boolean transitionStarted);
/**
* Interface to the Window Manager state associated with a particular
@@ -725,13 +728,6 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId);
/**
- * Set or clear a window which can behave as the keyguard.
- *
- * @param win The window which can behave as the keyguard.
- */
- void setKeyguardCandidateLw(@Nullable WindowState win);
-
- /**
* Create and return an animation to re-display a window that was force hidden by Keyguard.
*/
public Animation createHiddenByKeyguardExit(boolean onWallpaper,
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 0535af57d8ab..0080ec6cc989 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -29,7 +29,6 @@ import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
-import com.android.server.wm.WindowManagerService;
import java.io.PrintWriter;
@@ -67,7 +66,7 @@ public class KeyguardServiceDelegate {
boolean showing;
boolean showingAndNotOccluded;
boolean inputRestricted;
- boolean occluded;
+ volatile boolean occluded;
boolean secure;
boolean dreaming;
boolean systemIsReady;
@@ -235,13 +234,6 @@ public class KeyguardServiceDelegate {
return false;
}
- public boolean hasLockscreenWallpaper() {
- if (mKeyguardService != null) {
- return mKeyguardService.hasLockscreenWallpaper();
- }
- return false;
- }
-
public boolean hasKeyguard() {
return mKeyguardState.deviceHasKeyguard;
}
@@ -259,19 +251,18 @@ public class KeyguardServiceDelegate {
}
}
- /**
- * @deprecated Notify occlude status change via remote animation.
- */
- @Deprecated
- public void setOccluded(boolean isOccluded, boolean animate) {
- if (!WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
- && mKeyguardService != null) {
+ public void setOccluded(boolean isOccluded, boolean animate, boolean notify) {
+ if (mKeyguardService != null && notify) {
if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
mKeyguardService.setOccluded(isOccluded, animate);
}
mKeyguardState.occluded = isOccluded;
}
+ public boolean isOccluded() {
+ return mKeyguardState.occluded;
+ }
+
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
if (mKeyguardService != null) {
mKeyguardService.dismiss(callback, message);
@@ -406,8 +397,7 @@ public class KeyguardServiceDelegate {
}
public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
- if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation
- && mKeyguardService != null) {
+ if (mKeyguardService != null) {
mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 855a1ccc172d..ac650ec0f564 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -261,10 +261,6 @@ public class KeyguardServiceWrapper implements IKeyguardService {
return mKeyguardStateMonitor.isTrusted();
}
- public boolean hasLockscreenWallpaper() {
- return mKeyguardStateMonitor.hasLockscreenWallpaper();
- }
-
public boolean isSecure(int userId) {
return mKeyguardStateMonitor.isSecure(userId);
}
@@ -276,4 +272,4 @@ public class KeyguardServiceWrapper implements IKeyguardService {
public void dump(String prefix, PrintWriter pw) {
mKeyguardStateMonitor.dump(prefix, pw);
}
-} \ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
index add0b01f1879..e6511372d62c 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java
@@ -44,7 +44,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
private volatile boolean mSimSecure = true;
private volatile boolean mInputRestricted = true;
private volatile boolean mTrusted = false;
- private volatile boolean mHasLockscreenWallpaper = false;
private int mCurrentUserId;
@@ -79,10 +78,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
return mTrusted;
}
- public boolean hasLockscreenWallpaper() {
- return mHasLockscreenWallpaper;
- }
-
@Override // Binder interface
public void onShowingStateChanged(boolean showing) {
mIsShowing = showing;
@@ -110,11 +105,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub {
mCallback.onTrustedChanged();
}
- @Override // Binder interface
- public void onHasLockscreenWallpaperChanged(boolean hasLockscreenWallpaper) {
- mHasLockscreenWallpaper = hasLockscreenWallpaper;
- }
-
public interface StateCallback {
void onTrustedChanged();
void onShowingChanged();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 688a3b2f1d59..8c7d257d271b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -31,6 +31,8 @@ import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
import static android.os.PowerManagerInternal.wakefulnessToString;
+import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -104,6 +106,7 @@ import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.LockGuard;
@@ -1842,6 +1845,9 @@ public final class PowerManagerService extends SystemService
+ ", details=" + details
+ ")...");
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
+ // The instrument will be timed out automatically after 2 seconds.
+ LatencyTracker.getInstance(mContext)
+ .onActionStart(ACTION_TURN_ON_SCREEN, String.valueOf(groupId));
setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
opPackageName, details);
@@ -3225,6 +3231,7 @@ public final class PowerManagerService extends SystemService
&& mDisplayGroupPowerStateMapper.getWakefulnessLocked(
groupId) == WAKEFULNESS_AWAKE) {
mDisplayGroupPowerStateMapper.setPoweringOnLocked(groupId, false);
+ LatencyTracker.getInstance(mContext).onActionEnd(ACTION_TURN_ON_SCREEN);
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
final int latencyMs = (int) (mClock.uptimeMillis()
- mDisplayGroupPowerStateMapper.getLastPowerOnTimeLocked(groupId));
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index ef0079e0c01f..ca675973b2fd 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -35,7 +35,6 @@ import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
@@ -313,12 +312,12 @@ public final class PowerStatsLogger extends Handler {
return mStartWallTime;
}
- public PowerStatsLogger(Context context, File dataStoragePath,
+ public PowerStatsLogger(Context context, Looper looper, File dataStoragePath,
String meterFilename, String meterCacheFilename,
String modelFilename, String modelCacheFilename,
String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
- super(Looper.getMainLooper());
+ super(looper);
mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime();
if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime);
mPowerStatsHALWrapper = powerStatsHALWrapper;
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index bb52c1dc1a29..9953ca8f9b65 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -28,6 +28,7 @@ import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.UserHandle;
import android.power.PowerStatsInternal;
import android.util.Slog;
@@ -79,6 +80,9 @@ public class PowerStatsService extends SystemService {
private StatsPullAtomCallbackImpl mPullAtomCallback;
@Nullable
private PowerStatsInternal mPowerStatsInternal;
+ @Nullable
+ @GuardedBy("this")
+ private Looper mLooper;
@VisibleForTesting
static class Injector {
@@ -127,12 +131,12 @@ public class PowerStatsService extends SystemService {
}
}
- PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
- String meterFilename, String meterCacheFilename,
+ PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
+ File dataStoragePath, String meterFilename, String meterCacheFilename,
String modelFilename, String modelCacheFilename,
String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
- return new PowerStatsLogger(context, dataStoragePath,
+ return new PowerStatsLogger(context, looper, dataStoragePath,
meterFilename, meterCacheFilename,
modelFilename, modelCacheFilename,
residencyFilename, residencyCacheFilename,
@@ -229,11 +233,11 @@ public class PowerStatsService extends SystemService {
mDataStoragePath = mInjector.createDataStoragePath();
// Only start logger and triggers if initialization is successful.
- mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath,
- mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(),
- mInjector.createModelFilename(), mInjector.createModelCacheFilename(),
- mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(),
- getPowerStatsHal());
+ mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, getLooper(),
+ mDataStoragePath, mInjector.createMeterFilename(),
+ mInjector.createMeterCacheFilename(), mInjector.createModelFilename(),
+ mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(),
+ mInjector.createResidencyCacheFilename(), getPowerStatsHal());
mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
} else {
@@ -245,6 +249,17 @@ public class PowerStatsService extends SystemService {
return mInjector.getPowerStatsHALWrapperImpl();
}
+ private Looper getLooper() {
+ synchronized (this) {
+ if (mLooper == null) {
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ return thread.getLooper();
+ }
+ return mLooper;
+ }
+ }
+
public PowerStatsService(Context context) {
this(context, new Injector());
}
@@ -260,9 +275,7 @@ public class PowerStatsService extends SystemService {
private final Handler mHandler;
LocalService() {
- HandlerThread thread = new HandlerThread(TAG);
- thread.start();
- mHandler = new Handler(thread.getLooper());
+ mHandler = new Handler(getLooper());
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
index 212f81f72b24..086ebc99eff7 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Watchdog.java
@@ -27,8 +27,6 @@ import android.os.SystemProperties;
import android.util.Log;
import java.util.Objects;
-import java.util.Timer;
-import java.util.TimerTask;
/**
* An {@link ISoundTriggerHw2} decorator that would enforce deadlines on all calls and reboot the
@@ -38,14 +36,12 @@ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 {
private static final long TIMEOUT_MS = 3000;
private static final String TAG = "SoundTriggerHw2Watchdog";
- private final @NonNull
- ISoundTriggerHw2 mUnderlying;
- private final @NonNull
- Timer mTimer;
+ private final @NonNull ISoundTriggerHw2 mUnderlying;
+ private final @NonNull UptimeTimer mTimer;
public SoundTriggerHw2Watchdog(@NonNull ISoundTriggerHw2 underlying) {
mUnderlying = Objects.requireNonNull(underlying);
- mTimer = new Timer("SoundTriggerHw2Watchdog");
+ mTimer = new UptimeTimer("SoundTriggerHw2Watchdog");
}
@Override
@@ -149,21 +145,16 @@ public class SoundTriggerHw2Watchdog implements ISoundTriggerHw2 {
}
private class Watchdog implements AutoCloseable {
- private final @NonNull
- TimerTask mTask;
+ private final @NonNull UptimeTimer.Task mTask;
// This exception is used merely for capturing a stack trace at the time of creation.
private final @NonNull
Exception mException = new Exception();
Watchdog() {
- mTask = new TimerTask() {
- @Override
- public void run() {
- Log.e(TAG, "HAL deadline expired. Rebooting.", mException);
- rebootHal();
- }
- };
- mTimer.schedule(mTask, TIMEOUT_MS);
+ mTask = mTimer.createTask(() -> {
+ Log.e(TAG, "HAL deadline expired. Rebooting.", mException);
+ rebootHal();
+ }, TIMEOUT_MS);
}
@Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index 2b03fe88a1ec..3fbcd93302e7 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -138,6 +138,8 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware
// SoundTrigger data is treated the same as Hotword-source audio. This should incur the
// HOTWORD op instead of the RECORD_AUDIO op. The RECORD_AUDIO permission is still required,
// and since this is a data delivery check, soft denials aren't accepted.
+ // TODO(b/212458940): Find a better approach for checking the permission that doesn't
+ // require the client to know such details about the permissions logic.
enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO,
/* allowSoftDenial= */ false);
int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java
new file mode 100644
index 000000000000..bfcc7d840662
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/UptimeTimer.java
@@ -0,0 +1,90 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A simple timer, similar to java.util.Timer, but using the "uptime clock".
+ *
+ * Example usage:
+ * UptimeTimer timer = new UptimeTimer("TimerThread");
+ * UptimeTimer.Task task = timer.createTask(() -> { ... }, 100);
+ * ...
+ * // optionally, some time later:
+ * task.cancel();
+ */
+class UptimeTimer {
+ private Handler mHandler = null;
+
+ interface Task {
+ void cancel();
+ }
+
+ UptimeTimer(String threadName) {
+ new Thread(this::threadFunc, threadName).start();
+ synchronized (this) {
+ while (mHandler == null) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ Task createTask(@NonNull Runnable runnable, long uptimeMs) {
+ TaskImpl task = new TaskImpl(runnable);
+ mHandler.postDelayed(task, uptimeMs);
+ return task;
+ }
+
+ private void threadFunc() {
+ Looper.prepare();
+ synchronized (this) {
+ mHandler = new Handler(Looper.myLooper());
+ notifyAll();
+ }
+ Looper.loop();
+ }
+
+ private static class TaskImpl implements Task, Runnable {
+ private AtomicReference<Runnable> mRunnable = new AtomicReference<>();
+
+ TaskImpl(@NonNull Runnable runnable) {
+ mRunnable.set(runnable);
+ }
+
+ @Override
+ public void cancel() {
+ mRunnable.set(null);
+ }
+
+ @Override
+ public void run() {
+ Runnable runnable = mRunnable.get();
+ if (runnable != null) {
+ runnable.run();
+ }
+ }
+ }
+}
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 61770ea1c1c2..dfff76d3a044 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -29,6 +29,7 @@ import static android.net.NetworkIdentity.OEM_PAID;
import static android.net.NetworkIdentity.OEM_PRIVATE;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_YES;
import static android.net.NetworkStats.ROAMING_ALL;
import static android.net.NetworkTemplate.MATCH_ETHERNET;
import static android.net.NetworkTemplate.MATCH_MOBILE_WILDCARD;
@@ -49,6 +50,10 @@ import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import static android.util.MathUtils.constrain;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__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.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
@@ -67,6 +72,7 @@ import static java.util.concurrent.TimeUnit.MICROSECONDS;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.AppOpsManager.HistoricalOp;
@@ -82,6 +88,7 @@ import android.app.StatsManager.PullAtomMetadata;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.UidTraffic;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -734,6 +741,10 @@ public class StatsPullAtomService extends SystemService {
case FrameworkStatsLog.RKP_ERROR_STATS:
case FrameworkStatsLog.KEYSTORE2_CRASH_STATS:
return pullKeystoreAtoms(atomTag, data);
+ case FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS:
+ return pullAccessibilityShortcutStatsLocked(atomTag, data);
+ case FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS:
+ return pullAccessibilityFloatingMenuStatsLocked(atomTag, data);
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
}
@@ -930,6 +941,8 @@ public class StatsPullAtomService extends SystemService {
registerKeystoreKeyOperationWithGeneralInfo();
registerRkpErrorStats();
registerKeystoreCrashStats();
+ registerAccessibilityShortcutStats();
+ registerAccessibilityFloatingMenuStats();
}
private void initAndRegisterNetworkStatsPullers() {
@@ -1340,7 +1353,7 @@ public class StatsPullAtomService extends SystemService {
@Nullable private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) {
final NetworkTemplate template = (transport == TRANSPORT_CELLULAR)
? NetworkTemplate.buildTemplateMobileWithRatType(
- /*subscriptionId=*/null, NETWORK_TYPE_ALL)
+ /*subscriptionId=*/null, NETWORK_TYPE_ALL, METERED_YES)
: NetworkTemplate.buildTemplateWifiWildcard();
return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
}
@@ -1380,7 +1393,8 @@ public class StatsPullAtomService extends SystemService {
final List<NetworkStatsExt> ret = new ArrayList<>();
for (final int ratType : getAllCollapsedRatTypes()) {
final NetworkTemplate template =
- buildTemplateMobileWithRatType(subInfo.subscriberId, ratType);
+ buildTemplateMobileWithRatType(subInfo.subscriberId, ratType,
+ METERED_YES);
final NetworkStats stats =
getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
if (stats != null) {
@@ -4150,6 +4164,26 @@ public class StatsPullAtomService extends SystemService {
mStatsCallbackImpl);
}
+ private void registerAccessibilityShortcutStats() {
+ int tagId = FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
+ private void registerAccessibilityFloatingMenuStats() {
+ int tagId = FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl
+ );
+ }
+
int parseKeystoreStorageStats(KeystoreAtom[] atoms, List<StatsEvent> pulledData) {
for (KeystoreAtom atomWrapper : atoms) {
if (atomWrapper.payload.getTag() != KeystoreAtomPayload.storageStats) {
@@ -4341,6 +4375,158 @@ public class StatsPullAtomService extends SystemService {
}
}
+ int pullAccessibilityShortcutStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final ContentResolver resolver = mContext.getContentResolver();
+ final int hardware_shortcut_type =
+ FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
+ final int triple_tap_shortcut =
+ FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
+ for (UserInfo userInfo : userManager.getUsers()) {
+ final int userId = userInfo.getUserHandle().getIdentifier();
+
+ if (isAccessibilityShortcutUser(mContext, userId)) {
+ final int software_shortcut_type = convertToAccessibilityShortcutType(
+ Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId));
+ final String software_shortcut_list = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
+ final int software_shortcut_service_num = countAccessibilityServices(
+ software_shortcut_list);
+
+ final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ final int hardware_shortcut_service_num = countAccessibilityServices(
+ hardware_shortcut_list);
+
+ // only allow magnification to use it for now
+ final int triple_tap_service_num = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId);
+
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(atomTag,
+ software_shortcut_type, software_shortcut_service_num,
+ hardware_shortcut_type, hardware_shortcut_service_num,
+ triple_tap_shortcut, triple_tap_service_num));
+ }
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "pulling accessibility shortcuts stats failed at getUsers", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ int pullAccessibilityFloatingMenuStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final ContentResolver resolver = mContext.getContentResolver();
+ final int defaultSize = 0;
+ final int defaultIconType = 0;
+ final int defaultFadeEnabled = 1;
+ final float defaultOpacity = 0.55f;
+
+ for (UserInfo userInfo : userManager.getUsers()) {
+ final int userId = userInfo.getUserHandle().getIdentifier();
+
+ if (isAccessibilityFloatingMenuUser(mContext, userId)) {
+ final int size = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE, defaultSize, userId);
+ final int type = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
+ defaultIconType, userId);
+ final boolean fadeEnabled = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ defaultFadeEnabled, userId)) == 1;
+ final float opacity = Settings.Secure.getFloatForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
+ defaultOpacity, userId);
+
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(atomTag, size, type, fadeEnabled,
+ opacity));
+ }
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "pulling accessibility floating menu stats failed at getUsers", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ /**
+ * Counts how many accessibility services (including features) there are in the colon-separated
+ * string list.
+ *
+ * @param semicolonList colon-separated string, it should be
+ * {@link Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS} or
+ * {@link Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE}.
+ * @return The number of accessibility services
+ */
+ private int countAccessibilityServices(String semicolonList) {
+ if (TextUtils.isEmpty(semicolonList)) {
+ return 0;
+ }
+ final int semiColonNums = (int) semicolonList.chars().filter(ch -> ch == ':').count();
+ return TextUtils.isEmpty(semicolonList) ? 0 : semiColonNums + 1;
+ }
+
+ private boolean isAccessibilityShortcutUser(Context context, @UserIdInt int userId) {
+ final ContentResolver resolver = context.getContentResolver();
+
+ final String software_shortcut_list = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
+ final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ final boolean hardware_shortcut_dialog_shown = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId) == 1;
+ final boolean software_shortcut_enabled = !TextUtils.isEmpty(software_shortcut_list);
+ final boolean hardware_shortcut_enabled =
+ hardware_shortcut_dialog_shown && !TextUtils.isEmpty(hardware_shortcut_list);
+ final boolean triple_tap_shortcut_enabled = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId) == 1;
+
+ return software_shortcut_enabled || hardware_shortcut_enabled
+ || triple_tap_shortcut_enabled;
+ }
+
+ private boolean isAccessibilityFloatingMenuUser(Context context, @UserIdInt int userId) {
+ final ContentResolver resolver = context.getContentResolver();
+ final int mode = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId);
+ final String software_string = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
+
+ return (mode == Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU)
+ && !TextUtils.isEmpty(software_string);
+ }
+
+ private int convertToAccessibilityShortcutType(int shortcutType) {
+ switch (shortcutType) {
+ case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR:
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+ case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU:
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+ case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE:
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+ default:
+ return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+ }
+ }
+
// Thermal event received from vendor thermal management subsystem
private static final class ThermalEventListener extends IThermalEventListener.Stub {
@Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a436e6b3787b..411f3dcc1eb6 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -132,10 +133,11 @@ public interface StatusBarManagerInternal {
/** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, boolean isFullscreen);
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName);
/** @see com.android.internal.statusbar.IStatusBar#showTransient */
- void showTransient(int displayId, @InternalInsetsType int[] types);
+ void showTransient(int displayId, @InternalInsetsType int[] types,
+ boolean isGestureOnSystemBar);
/** @see com.android.internal.statusbar.IStatusBar#abortTransient */
void abortTransient(int displayId, @InternalInsetsType int[] types);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 45b83cf0454d..92d8d2f03841 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -60,6 +60,7 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -527,23 +528,25 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, boolean isFullscreen) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ String packageName) {
getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, isFullscreen);
+ navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
if (mBar != null) {
try {
mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, isFullscreen);
+ navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
} catch (RemoteException ex) { }
}
}
@Override
- public void showTransient(int displayId, @InternalInsetsType int[] types) {
+ public void showTransient(int displayId, @InternalInsetsType int[] types,
+ boolean isGestureOnSystemBar) {
getUiState(displayId).showTransient(types);
if (mBar != null) {
try {
- mBar.showTransient(displayId, types);
+ mBar.showTransient(displayId, types, isGestureOnSystemBar);
} catch (RemoteException ex) { }
}
}
@@ -1105,13 +1108,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
return state;
}
- private class UiState {
+ private static class UiState {
private @Appearance int mAppearance = 0;
private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
- private ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
+ private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
private boolean mNavbarColorManagedByIme = false;
private @Behavior int mBehavior;
- private boolean mFullscreen = false;
+ private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private String mPackageName = "none";
private int mDisabled1 = 0;
private int mDisabled2 = 0;
private int mImeWindowVis = 0;
@@ -1121,12 +1125,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
private void setBarAttributes(@Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, boolean isFullscreen) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+ String packageName) {
mAppearance = appearance;
mAppearanceRegions = appearanceRegions;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
- mFullscreen = isFullscreen;
+ mRequestedVisibilities = requestedVisibilities;
+ mPackageName = packageName;
}
private void showTransient(@InternalInsetsType int[] types) {
@@ -1246,8 +1252,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
state.mImeBackDisposition, state.mShowImeSwitcher,
gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
- state.mNavbarColorManagedByIme, state.mBehavior, state.mFullscreen,
- transientBarTypes);
+ state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
+ state.mPackageName, transientBarTypes);
}
}
diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java
index e0cc8e182079..f29c40f74353 100644
--- a/services/core/java/com/android/server/vcn/Vcn.java
+++ b/services/core/java/com/android/server/vcn/Vcn.java
@@ -39,10 +39,13 @@ import android.net.vcn.VcnConfig;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Message;
import android.os.ParcelUuid;
import android.provider.Settings;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -57,6 +60,7 @@ import java.util.Arrays;
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.Map.Entry;
@@ -148,6 +152,10 @@ public class Vcn extends Handler {
@NonNull private final VcnContentResolver mContentResolver;
@NonNull private final ContentObserver mMobileDataSettingsObserver;
+ @NonNull
+ private final Map<Integer, VcnUserMobileDataStateListener> mMobileDataStateListeners =
+ new ArrayMap<>();
+
/**
* Map containing all VcnGatewayConnections and their VcnGatewayConnectionConfigs.
*
@@ -221,6 +229,9 @@ public class Vcn extends Handler {
// Update mIsMobileDataEnabled before starting handling of NetworkRequests.
mIsMobileDataEnabled = getMobileDataStatus();
+ // Register mobile data state listeners.
+ updateMobileDataStateListeners();
+
// Register to receive cached and future NetworkRequests
mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener);
}
@@ -348,6 +359,12 @@ public class Vcn extends Handler {
gatewayConnection.teardownAsynchronously();
}
+ // Unregister MobileDataStateListeners
+ for (VcnUserMobileDataStateListener listener : mMobileDataStateListeners.values()) {
+ getTelephonyManager().unregisterTelephonyCallback(listener);
+ }
+ mMobileDataStateListeners.clear();
+
mCurrentStatus = VCN_STATUS_CODE_INACTIVE;
}
@@ -454,11 +471,40 @@ public class Vcn extends Handler {
gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot);
}
+ updateMobileDataStateListeners();
+
// Update the mobile data state after updating the subscription snapshot as a change in
// subIds for a subGroup may affect the mobile data state.
handleMobileDataToggled();
}
+ private void updateMobileDataStateListeners() {
+ final Set<Integer> subIdsInGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
+ final HandlerExecutor executor = new HandlerExecutor(this);
+
+ // Register new callbacks
+ for (int subId : subIdsInGroup) {
+ if (!mMobileDataStateListeners.containsKey(subId)) {
+ final VcnUserMobileDataStateListener listener =
+ new VcnUserMobileDataStateListener();
+
+ getTelephonyManagerForSubid(subId).registerTelephonyCallback(executor, listener);
+ mMobileDataStateListeners.put(subId, listener);
+ }
+ }
+
+ // Unregister old callbacks
+ Iterator<Entry<Integer, VcnUserMobileDataStateListener>> iterator =
+ mMobileDataStateListeners.entrySet().iterator();
+ while (iterator.hasNext()) {
+ final Entry<Integer, VcnUserMobileDataStateListener> entry = iterator.next();
+ if (!subIdsInGroup.contains(entry.getKey())) {
+ getTelephonyManager().unregisterTelephonyCallback(entry.getValue());
+ iterator.remove();
+ }
+ }
+ }
+
private void handleMobileDataToggled() {
final boolean oldMobileDataEnabledStatus = mIsMobileDataEnabled;
mIsMobileDataEnabled = getMobileDataStatus();
@@ -493,11 +539,8 @@ public class Vcn extends Handler {
}
private boolean getMobileDataStatus() {
- final TelephonyManager genericTelMan =
- mVcnContext.getContext().getSystemService(TelephonyManager.class);
-
for (int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
- if (genericTelMan.createForSubscriptionId(subId).isDataEnabled()) {
+ if (getTelephonyManagerForSubid(subId).isDataEnabled()) {
return true;
}
}
@@ -517,6 +560,14 @@ public class Vcn extends Handler {
return request.canBeSatisfiedBy(builder.build());
}
+ private TelephonyManager getTelephonyManager() {
+ return mVcnContext.getContext().getSystemService(TelephonyManager.class);
+ }
+
+ private TelephonyManager getTelephonyManagerForSubid(int subid) {
+ return getTelephonyManager().createForSubscriptionId(subid);
+ }
+
private String getLogPrefix() {
return "["
+ LogUtils.getHashedSubscriptionGroup(mSubscriptionGroup)
@@ -670,6 +721,16 @@ public class Vcn extends Handler {
}
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ class VcnUserMobileDataStateListener extends TelephonyCallback
+ implements TelephonyCallback.UserMobileDataStateListener {
+
+ @Override
+ public void onUserMobileDataStateChanged(boolean enabled) {
+ sendMessage(obtainMessage(MSG_EVENT_MOBILE_DATA_TOGGLED));
+ }
+ }
+
/** External dependencies used by Vcn, for injection in tests */
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class Dependencies {
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 7dec4e785f5c..51c3b33b0e79 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -77,6 +77,7 @@ import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
@@ -782,8 +783,19 @@ public class VcnGatewayConnection extends StateMachine {
// TODO(b/179091925): Move the delayed-message handling to BaseState
// If underlying is null, all underlying networks have been lost. Disconnect VCN after a
- // timeout.
+ // timeout (or immediately if in airplane mode, since the device user has indicated that
+ // the radios should all be turned off).
if (underlying == null) {
+ if (mDeps.isAirplaneModeOn(mVcnContext)) {
+ sendMessageAndAcquireWakeLock(
+ EVENT_UNDERLYING_NETWORK_CHANGED,
+ TOKEN_ALL,
+ new EventUnderlyingNetworkChangedInfo(null));
+ sendDisconnectRequestedAndAcquireWakelock(
+ DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */);
+ return;
+ }
+
setDisconnectRequestAlarm();
} else {
// Received a new Network so any previous alarm is irrelevant - cancel + clear it,
@@ -2414,6 +2426,12 @@ public class VcnGatewayConnection extends StateMachine {
validationStatusCallback);
}
+ /** Checks if airplane mode is enabled. */
+ public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) {
+ return Settings.Global.getInt(vcnContext.getContext().getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ }
+
/** Gets the elapsed real time since boot, in millis. */
public long getElapsedRealTime() {
return SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e447a23cf331..245f4538a076 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -44,9 +44,11 @@ final class Vibration {
enum Status {
RUNNING,
FINISHED,
+ FINISHED_UNEXPECTED, // Didn't terminate in the usual way.
FORWARDED_TO_INPUT_DEVICES,
CANCELLED,
IGNORED_ERROR_APP_OPS,
+ IGNORED_ERROR_TOKEN,
IGNORED,
IGNORED_APP_OPS,
IGNORED_BACKGROUND,
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 0f4f58b32c1b..25321c18bb3c 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -47,6 +47,7 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
@@ -98,7 +99,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
}
private final Object mLock = new Object();
- private final WorkSource mWorkSource = new WorkSource();
+ private final WorkSource mWorkSource;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
private final VibrationSettings mVibrationSettings;
@@ -110,6 +111,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
private volatile boolean mStop;
private volatile boolean mForceStop;
+ // Variable only set and read in main thread.
+ private boolean mCalledVibrationCompleteCallback = false;
VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
DeviceVibrationEffectAdapter effectAdapter,
@@ -119,9 +122,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
mVibrationSettings = vibrationSettings;
mDeviceEffectAdapter = effectAdapter;
mCallbacks = callbacks;
+ mWorkSource = new WorkSource(mVibration.uid);
mWakeLock = wakeLock;
- mWorkSource.set(vib.uid);
- mWakeLock.setWorkSource(mWorkSource);
mBatteryStatsService = batteryStatsService;
CombinedVibration effect = vib.getEffect();
@@ -151,17 +153,53 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
@Override
public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ // Structured to guarantee the vibrators completed and released callbacks at the end of
+ // thread execution. Both of these callbacks are exclusively called from this thread.
+ try {
+ try {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ runWithWakeLock();
+ } finally {
+ clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
+ }
+ } finally {
+ mCallbacks.onVibratorsReleased();
+ }
+ }
+
+ /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
+ private void runWithWakeLock() {
+ mWakeLock.setWorkSource(mWorkSource);
mWakeLock.acquire();
try {
+ runWithWakeLockAndDeathLink();
+ } finally {
+ mWakeLock.release();
+ }
+ }
+
+ /**
+ * Runs the VibrationThread with the binder death link, handling link/unlink failures.
+ * Called from within runWithWakeLock.
+ */
+ private void runWithWakeLockAndDeathLink() {
+ try {
mVibration.token.linkToDeath(this, 0);
- playVibration();
- mCallbacks.onVibratorsReleased();
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
+ clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
+ return;
+ }
+ // Ensure that the unlink always occurs now.
+ try {
+ // This is the actual execution of the vibration.
+ playVibration();
} finally {
- mVibration.token.unlinkToDeath(this, 0);
- mWakeLock.release();
+ try {
+ mVibration.token.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink token", e);
+ }
}
}
@@ -219,6 +257,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
}
}
+ // Indicate that the vibration is complete. This can be called multiple times only for
+ // convenience of handling error conditions - an error after the client is complete won't
+ // affect the status.
+ private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
+ if (!mCalledVibrationCompleteCallback) {
+ mCalledVibrationCompleteCallback = true;
+ mCallbacks.onVibrationCompleted(mVibration.id, completedStatus);
+ }
+ }
+
private void playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try {
@@ -226,7 +274,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
final int sequentialEffectSize = sequentialEffect.getEffects().size();
mStepQueue.offer(new StartVibrateStep(sequentialEffect));
- Vibration.Status status = null;
while (!mStepQueue.isEmpty()) {
long waitTime;
synchronized (mLock) {
@@ -242,13 +289,12 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
if (waitTime <= 0) {
mStepQueue.consumeNext();
}
- Vibration.Status currentStatus = mStop ? Vibration.Status.CANCELLED
+ Vibration.Status status = mStop ? Vibration.Status.CANCELLED
: mStepQueue.calculateVibrationStatus(sequentialEffectSize);
- if (status == null && currentStatus != Vibration.Status.RUNNING) {
+ if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
// First time vibration stopped running, start clean-up tasks and notify
// callback immediately.
- status = currentStatus;
- mCallbacks.onVibrationCompleted(mVibration.id, status);
+ clientVibrationCompleteIfNotAlready(status);
if (status == Vibration.Status.CANCELLED) {
mStepQueue.cancel();
}
@@ -256,19 +302,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
if (mForceStop) {
// Cancel every step and stop playing them right away, even clean-up steps.
mStepQueue.cancelImmediately();
+ clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
break;
}
}
-
- if (status == null) {
- status = mStepQueue.calculateVibrationStatus(sequentialEffectSize);
- if (status == Vibration.Status.RUNNING) {
- Slog.w(TAG, "Something went wrong, step queue completed but vibration status"
- + " is still RUNNING for vibration " + mVibration.id);
- status = Vibration.Status.FINISHED;
- }
- mCallbacks.onVibrationCompleted(mVibration.id, status);
- }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2a47512bb147..730766275f4a 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1319,24 +1319,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return IExternalVibratorService.SCALE_MUTE;
}
- int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
- vib.getVibrationAttributes());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
- vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
- if (mode == AppOpsManager.MODE_ERRORED) {
- Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
- } else {
- endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
- }
- return vibHolder.scale;
- }
-
ExternalVibrationHolder cancelingExternalVibration = null;
VibrationThread cancelingVibration = null;
int scale;
synchronized (mLock) {
+ Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
+ vib.getUid(), vib.getPackage(), vib.getVibrationAttributes());
+ if (ignoreStatus != null) {
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
+ vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
+ endVibrationLocked(vibHolder, ignoreStatus);
+ return vibHolder.scale;
+ }
if (mCurrentExternalVibration != null
&& mCurrentExternalVibration.externalVibration.equals(vib)) {
// We are already playing this external vibration, so we can return the same
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index a713e5b13667..39d7a1555dfc 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -302,7 +302,8 @@ class Vr2dDisplay {
builder.setUniqueId(UNIQUE_DISPLAY_ID);
builder.setFlags(flags);
mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
- builder.build(), null /* callback */, null /* handler */);
+ builder.build(), null /* callback */, null /* handler */,
+ null /* windowContext */);
if (mVirtualDisplay != null) {
updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
diff --git a/services/core/java/com/android/server/wallpaper/LocalColorRepository.java b/services/core/java/com/android/server/wallpaper/LocalColorRepository.java
new file mode 100644
index 000000000000..e6265f4dbd39
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/LocalColorRepository.java
@@ -0,0 +1,166 @@
+/*
+ * 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.wallpaper;
+
+import android.app.ILocalWallpaperColorConsumer;
+import android.graphics.RectF;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Manages the lifecycle of local wallpaper color callbacks and their interested wallpaper regions.
+ */
+public class LocalColorRepository {
+ /**
+ * Maps local wallpaper color callbacks' binders to their interested wallpaper regions, which
+ * are stored in a map of display Ids to wallpaper regions.
+ * binder callback -> [display id: int] -> areas
+ */
+ ArrayMap<IBinder, SparseArray<ArraySet<RectF>>> mLocalColorAreas = new ArrayMap();
+ RemoteCallbackList<ILocalWallpaperColorConsumer> mCallbacks = new RemoteCallbackList();
+
+ /**
+ * Add areas to a consumer
+ * @param consumer
+ * @param areas
+ * @param displayId
+ */
+ public void addAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas, int displayId) {
+ IBinder binder = consumer.asBinder();
+ SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
+ ArraySet<RectF> displayAreas = null;
+ if (displays == null) {
+ try {
+ consumer.asBinder().linkToDeath(() ->
+ mLocalColorAreas.remove(consumer.asBinder()), 0);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ displays = new SparseArray<>();
+ mLocalColorAreas.put(binder, displays);
+ } else {
+ displayAreas = displays.get(displayId);
+ }
+ if (displayAreas == null) {
+ displayAreas = new ArraySet(areas);
+ displays.put(displayId, displayAreas);
+ }
+
+ for (int i = 0; i < areas.size(); i++) {
+ displayAreas.add(areas.get(i));
+ }
+ mCallbacks.register(consumer);
+ }
+
+ /**
+ * remove an area for a consumer
+ * @param consumer
+ * @param areas
+ * @param displayId
+ * @return the areas that are removed from all callbacks
+ */
+ public List<RectF> removeAreas(ILocalWallpaperColorConsumer consumer, List<RectF> areas,
+ int displayId) {
+ IBinder binder = consumer.asBinder();
+ SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
+ ArraySet<RectF> registeredAreas = null;
+ if (displays != null) {
+ registeredAreas = displays.get(displayId);
+ if (registeredAreas == null) {
+ mCallbacks.unregister(consumer);
+ } else {
+ for (int i = 0; i < areas.size(); i++) {
+ registeredAreas.remove(areas.get(i));
+ }
+ if (registeredAreas.size() == 0) {
+ displays.remove(displayId);
+ }
+ }
+ if (displays.size() == 0) {
+ mLocalColorAreas.remove(binder);
+ mCallbacks.unregister(consumer);
+ }
+ } else {
+ mCallbacks.unregister(consumer);
+ }
+ ArraySet<RectF> purged = new ArraySet<>(areas);
+ for (int i = 0; i < mLocalColorAreas.size(); i++) {
+ for (int j = 0; j < mLocalColorAreas.valueAt(i).size(); j++) {
+ for (int k = 0; k < mLocalColorAreas.valueAt(i).valueAt(j).size(); k++) {
+ purged.remove(mLocalColorAreas.valueAt(i).valueAt(j).valueAt(k));
+ }
+ }
+ }
+ return new ArrayList(purged);
+ }
+
+ /**
+ * Return the local areas by display id
+ * @param displayId
+ * @return
+ */
+ public List<RectF> getAreasByDisplayId(int displayId) {
+ ArrayList<RectF> areas = new ArrayList();
+ for (int i = 0; i < mLocalColorAreas.size(); i++) {
+ SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.valueAt(i);
+ if (displays == null) continue;
+ ArraySet<RectF> displayAreas = displays.get(displayId);
+ if (displayAreas == null) continue;
+ for (int j = 0; j < displayAreas.size(); j++) {
+ areas.add(displayAreas.valueAt(j));
+ }
+ }
+ return areas;
+ }
+
+ /**
+ * invoke a callback for each area of interest
+ * @param callback
+ * @param area
+ * @param displayId
+ */
+ public void forEachCallback(Consumer<ILocalWallpaperColorConsumer> callback,
+ RectF area, int displayId) {
+ mCallbacks.broadcast(cb -> {
+ IBinder binder = cb.asBinder();
+ SparseArray<ArraySet<RectF>> displays = mLocalColorAreas.get(binder);
+ if (displays == null) return;
+ ArraySet<RectF> displayAreas = displays.get(displayId);
+ if (displayAreas != null && displayAreas.contains(area)) callback.accept(cb);
+ });
+ }
+
+ /**
+ * For testing
+ * @param callback
+ * @return if the callback is registered
+ */
+ @VisibleForTesting
+ protected boolean isCallbackAvailable(ILocalWallpaperColorConsumer callback) {
+ return mLocalColorAreas.get(callback.asBinder()) != null;
+ }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 2f353d19b5df..6fda72e1267b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -89,8 +89,6 @@ import android.service.wallpaper.IWallpaperService;
import android.service.wallpaper.WallpaperService;
import android.system.ErrnoException;
import android.system.Os;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
@@ -881,12 +879,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
private int mCurrentUserId = UserHandle.USER_NULL;
private boolean mInAmbientMode;
- private ArrayMap<IBinder, ArraySet<RectF>> mLocalColorCallbackAreas =
- new ArrayMap<>();
- private ArrayMap<RectF, RemoteCallbackList<ILocalWallpaperColorConsumer>>
- mLocalColorAreaCallbacks = new ArrayMap<>();
- private ArrayMap<Integer, ArraySet<RectF>> mLocalColorDisplayIdAreas = new ArrayMap<>();
- private ArrayMap<IBinder, Integer> mLocalColorCallbackDisplayId = new ArrayMap<>();
+ private LocalColorRepository mLocalColorRepo = new LocalColorRepository();
static class WallpaperData {
@@ -1305,24 +1298,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors,
int displayId) {
forEachDisplayConnector(displayConnector -> {
- if (displayConnector.mDisplayId == displayId) {
- RemoteCallbackList<ILocalWallpaperColorConsumer> callbacks;
- ArrayMap<IBinder, Integer> callbackDisplayIds;
- synchronized (mLock) {
- callbacks = mLocalColorAreaCallbacks.get(area);
- callbackDisplayIds = new ArrayMap<>(mLocalColorCallbackDisplayId);
+ Consumer<ILocalWallpaperColorConsumer> callback = cb -> {
+ try {
+ cb.onColorsChanged(area, colors);
+ } catch (RemoteException e) {
+ e.printStackTrace();
}
- if (callbacks == null) return;
- callbacks.broadcast(c -> {
- try {
- Integer targetDisplayId =
- callbackDisplayIds.get(c.asBinder());
- if (targetDisplayId == null) return;
- if (targetDisplayId == displayId) c.onColorsChanged(area, colors);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- });
+ };
+ synchronized (mLock) {
+ // it is safe to make an IPC call since it is one way (returns immediately)
+ mLocalColorRepo.forEachCallback(callback, area, displayId);
}
});
}
@@ -1491,10 +1476,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
- ArraySet<RectF> areas = mLocalColorDisplayIdAreas.get(displayId);
+ List<RectF> areas = mLocalColorRepo.getAreasByDisplayId(displayId);
if (areas != null && areas.size() != 0) {
try {
- connector.mEngine.addLocalColorsAreas(new ArrayList<>(areas));
+ connector.mEngine.addLocalColorsAreas(areas);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to register local colors areas", e);
}
@@ -2494,37 +2479,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
IWallpaperEngine engine = getEngine(which, userId, displayId);
if (engine == null) return;
- ArrayList<RectF> validAreas = new ArrayList<>(regions.size());
synchronized (mLock) {
- ArraySet<RectF> areas = mLocalColorCallbackAreas.get(callback);
- if (areas == null) areas = new ArraySet<>(regions.size());
- areas.addAll(regions);
- mLocalColorCallbackAreas.put(callback.asBinder(), areas);
+ mLocalColorRepo.addAreas(callback, regions, displayId);
}
- for (int i = 0; i < regions.size(); i++) {
- if (!LOCAL_COLOR_BOUNDS.contains(regions.get(i))) {
- continue;
- }
- RemoteCallbackList callbacks;
- synchronized (mLock) {
- callbacks = mLocalColorAreaCallbacks.get(
- regions.get(i));
- if (callbacks == null) {
- callbacks = new RemoteCallbackList();
- mLocalColorAreaCallbacks.put(regions.get(i), callbacks);
- }
- mLocalColorCallbackDisplayId.put(callback.asBinder(), displayId);
- ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
- if (displayAreas == null) {
- displayAreas = new ArraySet<>(1);
- mLocalColorDisplayIdAreas.put(displayId, displayAreas);
- }
- displayAreas.add(regions.get(i));
- }
- validAreas.add(regions.get(i));
- callbacks.register(callback);
- }
- engine.addLocalColorsAreas(validAreas);
+ engine.addLocalColorsAreas(regions);
}
@Override
@@ -2539,45 +2497,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
throw new SecurityException("calling user id does not match");
}
final long identity = Binder.clearCallingIdentity();
- ArrayList<RectF> purgeAreas = new ArrayList<>();
- IBinder binder = callback.asBinder();
+ List<RectF> purgeAreas = null;
try {
synchronized (mLock) {
- ArraySet<RectF> currentAreas = mLocalColorCallbackAreas.get(binder);
- if (currentAreas == null) return;
- currentAreas.removeAll(removeAreas);
- if (currentAreas.size() == 0) {
- mLocalColorCallbackDisplayId.remove(binder);
- for (RectF removeArea : removeAreas) {
- RemoteCallbackList<ILocalWallpaperColorConsumer> remotes =
- mLocalColorAreaCallbacks.get(removeArea);
- if (remotes == null) continue;
- remotes.unregister(callback);
- if (remotes.getRegisteredCallbackCount() == 0) {
- mLocalColorAreaCallbacks.remove(removeArea);
- purgeAreas.add(removeArea);
- ArraySet<RectF> displayAreas = mLocalColorDisplayIdAreas.get(displayId);
- if (displayAreas != null) {
- displayAreas.remove(removeArea);
- if (displayAreas.size() == 0) {
- mLocalColorDisplayIdAreas.remove(displayId);
- }
- }
- }
- }
- }
+ purgeAreas = mLocalColorRepo.removeAreas(callback, removeAreas, displayId);
}
-
} catch (Exception e) {
// ignore any exception
} finally {
Binder.restoreCallingIdentity(identity);
}
-
- if (purgeAreas.size() == 0) return;
IWallpaperEngine engine = getEngine(which, userId, displayId);
- if (engine == null) return;
- engine.removeLocalColorsAreas(purgeAreas);
+ if (engine == null || purgeAreas == null) return;
+ if (purgeAreas.size() > 0) engine.removeLocalColorsAreas(purgeAreas);
}
@Override
@@ -3075,12 +3007,35 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ private boolean packageBelongsToUid(String packageName, int uid) {
+ int userId = UserHandle.getUserId(uid);
+ int packageUid;
+ try {
+ packageUid = mContext.getPackageManager().getPackageUidAsUser(
+ packageName, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return packageUid == uid;
+ }
+
+ private void enforcePackageBelongsToUid(String packageName, int uid) {
+ if (!packageBelongsToUid(packageName, uid)) {
+ throw new IllegalArgumentException(
+ "Invalid package or package does not belong to uid:"
+ + uid);
+ }
+ }
+
/**
* Certain user types do not support wallpapers (e.g. managed profiles). The check is
* implemented through through the OP_WRITE_WALLPAPER AppOp.
*/
public boolean isWallpaperSupported(String callingPackage) {
- return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, Binder.getCallingUid(),
+ final int callingUid = Binder.getCallingUid();
+ enforcePackageBelongsToUid(callingPackage, callingUid);
+
+ return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_WALLPAPER, callingUid,
callingPackage) == AppOpsManager.MODE_ALLOWED;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index a24319f7a98c..38a48570ba16 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,13 +16,15 @@
package com.android.server.wm;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CALLBACK;
+import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK;
import static android.os.Build.IS_USER;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+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.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
@@ -34,17 +36,21 @@ import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_P
import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_PKG;
import static com.android.server.accessibility.AccessibilityTraceProto.CALLING_STACKS;
import static com.android.server.accessibility.AccessibilityTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.accessibility.AccessibilityTraceProto.LOGGING_TYPE;
import static com.android.server.accessibility.AccessibilityTraceProto.PROCESS_NAME;
import static com.android.server.accessibility.AccessibilityTraceProto.THREAD_ID_NAME;
import static com.android.server.accessibility.AccessibilityTraceProto.WHERE;
import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
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.WindowTracing.WINSCOPE_EXT;
import static com.android.server.wm.utils.RegionUtils.forEachRect;
+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;
@@ -84,12 +90,14 @@ import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
+import android.view.WindowManagerPolicyConstants;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.TraceBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
@@ -99,8 +107,6 @@ import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallba
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -116,32 +122,36 @@ final class AccessibilityController {
private static final String TAG = AccessibilityController.class.getSimpleName();
private static final Object STATIC_LOCK = new Object();
- static AccessibilityControllerInternal
+ static AccessibilityControllerInternalImpl
getAccessibilityControllerInternal(WindowManagerService service) {
return AccessibilityControllerInternalImpl.getInstance(service);
}
- private final AccessibilityTracing mAccessibilityTracing;
+ private final AccessibilityControllerInternalImpl mAccessibilityTracing;
private final WindowManagerService mService;
private static final Rect EMPTY_RECT = new Rect();
private static final float[] sTempFloats = new float[9];
- AccessibilityController(WindowManagerService service) {
- mService = service;
- mAccessibilityTracing = AccessibilityTracing.getInstance(service);
- }
-
private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
new SparseArray<>();
+ private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
+ private int mFocusedDisplay = -1;
// Set to true if initializing window population complete.
private boolean mAllObserversInitialized = true;
+ AccessibilityController(WindowManagerService service) {
+ mService = service;
+ mAccessibilityTracing =
+ AccessibilityController.getAccessibilityControllerInternal(service);
+ }
+
boolean setMagnificationCallbacks(int displayId, MagnificationCallbacks callbacks) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(
TAG + ".setMagnificationCallbacks",
+ FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId + "; callbacks={" + callbacks + "}");
}
boolean result = false;
@@ -172,25 +182,31 @@ final class AccessibilityController {
/**
* Sets a callback for observing which windows are touchable for the purposes
- * of accessibility on specified display.
+ * of accessibility on specified display. When a display is reparented and becomes
+ * an embedded one, the {@link WindowsForAccessibilityCallback#onDisplayReparented(int)}
+ * will notify the accessibility framework to remove the un-used window observer of
+ * this embedded display.
*
* @param displayId The logical display id.
* @param callback The callback.
- * @return {@code false} if display id is not valid or an embedded display.
+ * @return {@code false} if display id is not valid or an embedded display when the callback
+ * isn't null.
*/
boolean setWindowsForAccessibilityCallback(int displayId,
WindowsForAccessibilityCallback callback) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(
TAG + ".setWindowsForAccessibilityCallback",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
"displayId=" + displayId + "; callback={" + callback + "}");
}
- final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
- if (dc == null) {
- return false;
- }
if (callback != null) {
+ final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+ if (dc == null) {
+ return false;
+ }
+
WindowsForAccessibilityObserver observer =
mWindowsForAccessibilityObserver.get(displayId);
if (isEmbeddedDisplay(dc)) {
@@ -209,21 +225,13 @@ final class AccessibilityController {
if (Build.IS_DEBUGGABLE) {
throw new IllegalStateException(errorMessage);
}
- removeObserverOfEmbeddedDisplay(observer);
+ removeObserversForEmbeddedChildDisplays(observer);
mWindowsForAccessibilityObserver.remove(displayId);
}
observer = new WindowsForAccessibilityObserver(mService, displayId, callback);
mWindowsForAccessibilityObserver.put(displayId, observer);
mAllObserversInitialized &= observer.mInitialized;
} else {
- if (isEmbeddedDisplay(dc)) {
- // If this display is an embedded one, its window observer should be removed along
- // with the window observer of its parent display removed because the window
- // observer of the embedded display and its parent display is the same, and would
- // be removed together when stopping the window tracking of its parent display. So
- // here don't need to do removing window observer of the embedded display again.
- return true;
- }
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver == null) {
@@ -234,16 +242,17 @@ final class AccessibilityController {
throw new IllegalStateException(errorMessage);
}
}
- removeObserverOfEmbeddedDisplay(windowsForA11yObserver);
+ removeObserversForEmbeddedChildDisplays(windowsForA11yObserver);
mWindowsForAccessibilityObserver.remove(displayId);
}
return true;
}
void performComputeChangedWindowsNot(int displayId, boolean forceSend) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(
TAG + ".performComputeChangedWindowsNot",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
"displayId=" + displayId + "; forceSend=" + forceSend);
}
WindowsForAccessibilityObserver observer = null;
@@ -260,8 +269,10 @@ final class AccessibilityController {
}
void setMagnificationSpec(int displayId, MagnificationSpec spec) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".setMagnificationSpec",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+ | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".setMagnificationSpec",
+ FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
"displayId=" + displayId + "; spec={" + spec + "}");
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -276,8 +287,9 @@ final class AccessibilityController {
}
void getMagnificationRegion(int displayId, Region outMagnificationRegion) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".getMagnificationRegion",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".getMagnificationRegion",
+ FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId + "; outMagnificationRegion={" + outMagnificationRegion
+ "}");
}
@@ -288,9 +300,10 @@ final class AccessibilityController {
}
void onRectangleOnScreenRequested(int displayId, Rect rectangle) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(
TAG + ".onRectangleOnScreenRequested",
+ FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId + "; rectangle={" + rectangle + "}");
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -301,9 +314,11 @@ final class AccessibilityController {
}
void onWindowLayersChanged(int displayId) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- TAG + ".onWindowLayersChanged", "displayId=" + 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) {
@@ -316,15 +331,18 @@ final class AccessibilityController {
}
}
- void onRotationChanged(DisplayContent displayContent) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".onRotationChanged",
+ void onDisplaySizeChanged(DisplayContent displayContent) {
+
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+ | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".onRotationChanged",
+ FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
"displayContent={" + displayContent + "}");
}
final int displayId = displayContent.getDisplayId();
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.onRotationChanged(displayContent);
+ displayMagnifier.onDisplaySizeChanged(displayContent);
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
@@ -334,8 +352,9 @@ final class AccessibilityController {
}
void onAppWindowTransition(int displayId, int transition) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".onAppWindowTransition",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".onAppWindowTransition",
+ FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId + "; transition=" + transition);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -346,8 +365,10 @@ final class AccessibilityController {
}
void onWindowTransition(WindowState windowState, int transition) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".onWindowTransition",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+ | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".onWindowTransition",
+ FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
"windowState={" + windowState + "}; transition=" + transition);
}
final int displayId = windowState.getDisplayId();
@@ -364,9 +385,9 @@ final class AccessibilityController {
void onWindowFocusChangedNot(int displayId) {
// Not relevant for the display magnifier.
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- TAG + ".onWindowFocusChangedNot", "displayId=" + displayId);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".onWindowFocusChangedNot",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "displayId=" + displayId);
}
WindowsForAccessibilityObserver observer = null;
synchronized (mService.mGlobalLock) {
@@ -426,12 +447,10 @@ final class AccessibilityController {
}
void onSomeWindowResizedOrMovedWithCallingUid(int callingUid, int... displayIds) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- TAG + ".onSomeWindowResizedOrMoved",
- "displayIds={" + displayIds.toString() + "}",
- "".getBytes(),
- callingUid);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".onSomeWindowResizedOrMoved",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
+ "displayIds={" + displayIds.toString() + "}", "".getBytes(), callingUid);
}
// Not relevant for the display magnifier.
for (int i = 0; i < displayIds.length; i++) {
@@ -444,9 +463,10 @@ final class AccessibilityController {
}
void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(
TAG + ".drawMagnifiedRegionBorderIfNeeded",
+ FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId + "; transaction={" + t + "}");
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -457,8 +477,9 @@ final class AccessibilityController {
}
MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".getMagnificationSpecForWindow",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow",
+ FLAGS_MAGNIFICATION_CALLBACK,
"windowState={" + windowState + "}");
}
final int displayId = windowState.getDisplayId();
@@ -470,17 +491,19 @@ final class AccessibilityController {
}
boolean hasCallbacks() {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".hasCallbacks");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+ | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".hasCallbacks",
+ FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK);
}
return (mDisplayMagnifiers.size() > 0
|| mWindowsForAccessibilityObserver.size() > 0);
}
void setForceShowMagnifiableBounds(int displayId, boolean show) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".setForceShowMagnifiableBounds",
- "displayId=" + displayId + "; show=" + show);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".setForceShowMagnifiableBounds",
+ FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; show=" + show);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
@@ -497,39 +520,50 @@ final class AccessibilityController {
void handleWindowObserverOfEmbeddedDisplay(
int embeddedDisplayId, WindowState parentWindow, int callingUid) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".handleWindowObserverOfEmbeddedDisplay",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".handleWindowObserverOfEmbeddedDisplay",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
"embeddedDisplayId=" + embeddedDisplayId + "; parentWindowState={"
- + parentWindow + "}",
- "".getBytes(),
- callingUid);
+ + parentWindow + "}", "".getBytes(), callingUid);
}
if (embeddedDisplayId == Display.DEFAULT_DISPLAY || parentWindow == null) {
return;
}
- // Finds the parent display of this embedded display
- final int parentDisplayId;
- WindowState candidate = parentWindow;
- while (candidate != null) {
- parentWindow = candidate;
- candidate = parentWindow.getDisplayContent().getParentWindow();
+ mService.mH.sendMessage(PooledLambda.obtainMessage(
+ AccessibilityController::updateWindowObserverOfEmbeddedDisplay,
+ this, embeddedDisplayId, parentWindow));
+ }
+
+ private void updateWindowObserverOfEmbeddedDisplay(int embeddedDisplayId,
+ WindowState parentWindow) {
+ final WindowsForAccessibilityObserver windowsForA11yObserver;
+
+ synchronized (mService.mGlobalLock) {
+ // Finds the parent display of this embedded display
+ WindowState candidate = parentWindow;
+ while (candidate != null) {
+ parentWindow = candidate;
+ candidate = parentWindow.getDisplayContent().getParentWindow();
+ }
+ final int parentDisplayId = parentWindow.getDisplayId();
+ // Uses the observer of parent display
+ windowsForA11yObserver = mWindowsForAccessibilityObserver.get(parentDisplayId);
}
- parentDisplayId = parentWindow.getDisplayId();
- // Uses the observer of parent display
- final WindowsForAccessibilityObserver windowsForA11yObserver =
- mWindowsForAccessibilityObserver.get(parentDisplayId);
if (windowsForA11yObserver != null) {
+ windowsForA11yObserver.notifyDisplayReparented(embeddedDisplayId);
windowsForA11yObserver.addEmbeddedDisplay(embeddedDisplayId);
- // Replaces the observer of embedded display to the one of parent display
- mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
+ synchronized (mService.mGlobalLock) {
+ // Replaces the observer of embedded display to the one of parent display
+ mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
+ }
}
}
void onImeSurfaceShownChanged(WindowState windowState, boolean shown) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(TAG + ".onImeSurfaceShownChanged",
- "windowState=" + windowState + "; shown=" + shown);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(TAG + ".onImeSurfaceShownChanged",
+ FLAGS_MAGNIFICATION_CALLBACK, "windowState=" + windowState + ";shown=" + shown);
}
final int displayId = windowState.getDisplayId();
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
@@ -555,7 +589,7 @@ final class AccessibilityController {
+ "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
}
- private void removeObserverOfEmbeddedDisplay(WindowsForAccessibilityObserver
+ private void removeObserversForEmbeddedChildDisplays(WindowsForAccessibilityObserver
observerOfParentDisplay) {
final IntArray embeddedDisplayIdList =
observerOfParentDisplay.getAndClearEmbeddedDisplayIdList();
@@ -572,6 +606,29 @@ final class AccessibilityController {
return display.getType() == Display.TYPE_VIRTUAL && dc.getParentWindow() != null;
}
+ void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) {
+ if (lastTarget != null) {
+ mFocusedWindow.remove(lastTarget.getDisplayId());
+ }
+ if (newTarget != null) {
+ int displayId = newTarget.getDisplayId();
+ IBinder clientBinder = newTarget.getIWindow().asBinder();
+ mFocusedWindow.put(displayId, clientBinder);
+ }
+ }
+
+ public void onDisplayRemoved(int displayId) {
+ mFocusedWindow.remove(displayId);
+ }
+
+ public void setFocusedDisplay(int focusedDisplayId) {
+ mFocusedDisplay = focusedDisplayId;
+ }
+
+ @Nullable IBinder getFocusedWindowToken() {
+ return mFocusedWindow.get(mFocusedDisplay);
+ }
+
/**
* This class encapsulates the functionality related to display magnification.
*/
@@ -580,7 +637,7 @@ final class AccessibilityController {
private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "DisplayMagnifier" : TAG_WM;
private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
- private static final boolean DEBUG_ROTATION = false;
+ private static final boolean DEBUG_DISPLAY_SIZE = false;
private static final boolean DEBUG_LAYERS = false;
private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
private static final boolean DEBUG_VIEWPORT_WINDOW = false;
@@ -599,7 +656,7 @@ final class AccessibilityController {
private final Handler mHandler;
private final DisplayContent mDisplayContent;
private final Display mDisplay;
- private final AccessibilityTracing mAccessibilityTracing;
+ private final AccessibilityControllerInternalImpl mAccessibilityTracing;
private final MagnificationCallbacks mCallbacks;
@@ -618,11 +675,13 @@ final class AccessibilityController {
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mMagnifedViewport = new MagnifiedViewport();
- mAccessibilityTracing = AccessibilityTracing.getInstance(mService);
+ mAccessibilityTracing =
+ AccessibilityController.getAccessibilityControllerInternal(mService);
mLongAnimationDuration = mDisplayContext.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".DisplayMagnifier.constructor",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".DisplayMagnifier.constructor",
+ FLAGS_MAGNIFICATION_CALLBACK,
"windowManagerService={" + windowManagerService + "}; displayContent={"
+ displayContent + "}; display={" + display + "}; callbacks={"
+ callbacks + "}");
@@ -630,9 +689,9 @@ final class AccessibilityController {
}
void setMagnificationSpec(MagnificationSpec spec) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- LOG_TAG + ".setMagnificationSpec", "spec={" + spec + "}");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".setMagnificationSpec",
+ FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
}
mMagnifedViewport.updateMagnificationSpec(spec);
mMagnifedViewport.recomputeBounds();
@@ -642,25 +701,26 @@ final class AccessibilityController {
}
void setForceShowMagnifiableBounds(boolean show) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- LOG_TAG + ".setForceShowMagnifiableBounds", "show=" + show);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
+ FLAGS_MAGNIFICATION_CALLBACK, "show=" + show);
}
mForceShowMagnifiableBounds = show;
mMagnifedViewport.setMagnifiedRegionBorderShown(show, true);
}
boolean isForceShowingMagnifiableBounds() {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".isForceShowingMagnifiableBounds");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".isForceShowingMagnifiableBounds",
+ FLAGS_MAGNIFICATION_CALLBACK);
}
return mForceShowMagnifiableBounds;
}
void onRectangleOnScreenRequested(Rect rectangle) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- LOG_TAG + ".onRectangleOnScreenRequested", "rectangle={" + rectangle + "}");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".onRectangleOnScreenRequested",
+ FLAGS_MAGNIFICATION_CALLBACK, "rectangle={" + rectangle + "}");
}
if (DEBUG_RECTANGLE_REQUESTED) {
Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
@@ -683,8 +743,9 @@ final class AccessibilityController {
}
void onWindowLayersChanged() {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".onWindowLayersChanged");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(
+ LOG_TAG + ".onWindowLayersChanged", FLAGS_MAGNIFICATION_CALLBACK);
}
if (DEBUG_LAYERS) {
Slog.i(LOG_TAG, "Layers changed.");
@@ -693,23 +754,24 @@ final class AccessibilityController {
mService.scheduleAnimationLocked();
}
- void onRotationChanged(DisplayContent displayContent) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- LOG_TAG + ".onRotationChanged", "displayContent={" + displayContent + "}");
+ void onDisplaySizeChanged(DisplayContent displayContent) {
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".onDisplaySizeChanged",
+ FLAGS_MAGNIFICATION_CALLBACK, "displayContent={" + displayContent + "}");
}
- if (DEBUG_ROTATION) {
+ if (DEBUG_DISPLAY_SIZE) {
final int rotation = displayContent.getRotation();
Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
+ " displayId: " + displayContent.getDisplayId());
}
- mMagnifedViewport.onRotationChanged();
- mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
+ mMagnifedViewport.onDisplaySizeChanged();
+ mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
void onAppWindowTransition(int displayId, int transition) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".onAppWindowTransition",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".onAppWindowTransition",
+ FLAGS_MAGNIFICATION_CALLBACK,
"displayId=" + displayId + "; transition=" + transition);
}
if (DEBUG_WINDOW_TRANSITIONS) {
@@ -721,6 +783,7 @@ final class AccessibilityController {
if (magnifying) {
switch (transition) {
case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
+ case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
case WindowManager.TRANSIT_OLD_TASK_OPEN:
case WindowManager.TRANSIT_OLD_TASK_TO_FRONT:
case WindowManager.TRANSIT_OLD_WALLPAPER_OPEN:
@@ -733,8 +796,9 @@ final class AccessibilityController {
}
void onWindowTransition(WindowState windowState, int transition) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".onWindowTransition",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".onWindowTransition",
+ FLAGS_MAGNIFICATION_CALLBACK,
"windowState={" + windowState + "}; transition=" + transition);
}
if (DEBUG_WINDOW_TRANSITIONS) {
@@ -791,18 +855,18 @@ final class AccessibilityController {
}
void onImeSurfaceShownChanged(boolean shown) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- LOG_TAG + ".onImeSurfaceShownChanged", "shown=" + shown);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".onImeSurfaceShownChanged",
+ FLAGS_MAGNIFICATION_CALLBACK, "shown=" + shown);
}
mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED,
shown ? 1 : 0, 0).sendToTarget();
}
MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationSpecForWindow",
- "windowState={" + windowState + "}");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationSpecForWindow",
+ FLAGS_MAGNIFICATION_CALLBACK, "windowState={" + windowState + "}");
}
MagnificationSpec spec = mMagnifedViewport.getMagnificationSpec();
if (spec != null && !spec.isNop()) {
@@ -814,8 +878,9 @@ final class AccessibilityController {
}
void getMagnificationRegion(Region outMagnificationRegion) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".getMagnificationRegion",
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".getMagnificationRegion",
+ FLAGS_MAGNIFICATION_CALLBACK,
"outMagnificationRegion={" + outMagnificationRegion + "}");
}
// Make sure we're working with the most current bounds
@@ -824,25 +889,26 @@ final class AccessibilityController {
}
void destroy() {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".destroy");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
}
mMagnifedViewport.destroyWindow();
}
// Can be called outside of a surface transaction
void showMagnificationBoundsIfNeeded() {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".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 drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
- "transition={" + t + "}");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
+ FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
}
mMagnifedViewport.drawWindowIfNeeded(t);
}
@@ -887,7 +953,8 @@ final class AccessibilityController {
if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
mCircularPath = new Path();
- mDisplay.getRealSize(mScreenSize);
+
+ getDisplaySizeLocked(mScreenSize);
final int centerXY = mScreenSize.x / 2;
mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
} else {
@@ -917,7 +984,7 @@ final class AccessibilityController {
}
void recomputeBounds() {
- mDisplay.getRealSize(mScreenSize);
+ getDisplaySizeLocked(mScreenSize);
final int screenWidth = mScreenSize.x;
final int screenHeight = mScreenSize.y;
@@ -942,6 +1009,8 @@ final class AccessibilityController {
final int windowType = windowState.mAttrs.type;
if (isExcludedWindowType(windowType)
|| ((windowState.mAttrs.privateFlags
+ & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
+ || ((windowState.mAttrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
continue;
}
@@ -1006,7 +1075,6 @@ final class AccessibilityController {
}
}
}
-
visibleWindows.clear();
mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
@@ -1043,18 +1111,16 @@ final class AccessibilityController {
private boolean isExcludedWindowType(int windowType) {
return windowType == TYPE_MAGNIFICATION_OVERLAY
- // Omit the touch region to avoid the cut out of the magnification
- // bounds because nav bar panel is unmagnifiable.
- || windowType == TYPE_NAVIGATION_BAR_PANEL
// Omit the touch region of window magnification to avoid the cut out of the
// magnification and the magnified center of window magnification could be
// in the bounds
|| windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
}
- void onRotationChanged() {
+ void onDisplaySizeChanged() {
// If we are showing the magnification border, hide it immediately so
- // the user does not see strange artifacts during rotation. The screenshot
+ // 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 complete
// we will show the border.
if (isMagnifying() || isForceShowingMagnifiableBounds()) {
@@ -1112,6 +1178,12 @@ final class AccessibilityController {
}, false /* traverseTopToBottom */ );
}
+ private void getDisplaySizeLocked(Point outSize) {
+ final Rect bounds =
+ mDisplayContent.getConfiguration().windowConfiguration.getBounds();
+ outSize.set(bounds.width(), bounds.height());
+ }
+
void dump(PrintWriter pw, String prefix) {
mWindow.dump(pw, prefix);
}
@@ -1155,7 +1227,7 @@ final class AccessibilityController {
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
final int layer =
mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) *
- WindowManagerService.TYPE_LAYER_MULTIPLIER;
+ WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0);
InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
mDisplayContent.getDisplayId(), "Magnification Overlay");
@@ -1226,7 +1298,7 @@ final class AccessibilityController {
void updateSize() {
synchronized (mService.mGlobalLock) {
- mDisplay.getRealSize(mScreenSize);
+ getDisplaySizeLocked(mScreenSize);
mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y,
PixelFormat.RGBA_8888);
invalidate(mDirtyRect);
@@ -1365,7 +1437,7 @@ final class AccessibilityController {
public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
- public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+ public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
@@ -1397,9 +1469,8 @@ final class AccessibilityController {
mCallbacks.onUserContextChanged();
} break;
- case MESSAGE_NOTIFY_ROTATION_CHANGED: {
- final int rotation = message.arg1;
- mCallbacks.onRotationChanged(rotation);
+ case MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED: {
+ mCallbacks.onDisplaySizeChanged();
} break;
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
@@ -1482,7 +1553,7 @@ final class AccessibilityController {
private final Handler mHandler;
- private final AccessibilityTracing mAccessibilityTracing;
+ private final AccessibilityControllerInternalImpl mAccessibilityTracing;
private final WindowsForAccessibilityCallback mCallback;
@@ -1502,24 +1573,26 @@ final class AccessibilityController {
mCallback = callback;
mDisplayId = displayId;
mHandler = new MyHandler(mService.mH.getLooper());
- mAccessibilityTracing = AccessibilityTracing.getInstance(mService);
+ mAccessibilityTracing =
+ AccessibilityController.getAccessibilityControllerInternal(mService);
mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
.getSendRecurringAccessibilityEventsInterval();
computeChangedWindows(true);
}
void performComputeChangedWindows(boolean forceSend) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".performComputeChangedWindows",
- "forceSend=" + forceSend);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".performComputeChangedWindows",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "forceSend=" + forceSend);
}
mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
computeChangedWindows(forceSend);
}
void scheduleComputeChangedWindows() {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(LOG_TAG + ".scheduleComputeChangedWindows");
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".scheduleComputeChangedWindows",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK);
}
if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
@@ -1542,6 +1615,13 @@ final class AccessibilityController {
mEmbeddedDisplayIdList.add(displayId);
}
+ void notifyDisplayReparented(int embeddedDisplayId) {
+ // Notifies the A11y framework the display is reparented and
+ // becomes an embedded display for removing the un-used
+ // displayWindowObserver of this embedded one.
+ mCallback.onDisplayReparented(embeddedDisplayId);
+ }
+
boolean shellRootIsAbove(WindowState windowState, ShellRoot shellRoot) {
int wsLayer = mService.mPolicy.getWindowLayerLw(windowState);
int shellLayer = mService.mPolicy.getWindowLayerFromTypeLw(shellRoot.getWindowType(),
@@ -1594,9 +1674,9 @@ final class AccessibilityController {
* @param forceSend Send the windows the accessibility even if they haven't changed.
*/
void computeChangedWindows(boolean forceSend) {
- if (mAccessibilityTracing.isEnabled()) {
- mAccessibilityTracing.logState(
- LOG_TAG + ".computeChangedWindows", "forceSend=" + forceSend);
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".computeChangedWindows",
+ FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK, "forceSend=" + forceSend);
}
if (DEBUG) {
Slog.i(LOG_TAG, "computeChangedWindows()");
@@ -1645,7 +1725,7 @@ final class AccessibilityController {
boolean focusedWindowAdded = false;
final int visibleWindowCount = visibleWindows.size();
- HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>();
+ ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments = new ArrayList<>();
ArrayList<ShellRoot> shellRoots = getSortedShellRoots(dc.mShellRoots);
@@ -1667,10 +1747,10 @@ final class AccessibilityController {
computeWindowRegionInScreen(windowState, regionInScreen);
if (windowMattersToAccessibility(windowState, regionInScreen, unaccountedSpace,
- skipRemainingWindowsForTasks)) {
+ skipRemainingWindowsForTaskFragments)) {
addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows);
updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace,
- skipRemainingWindowsForTasks);
+ skipRemainingWindowsForTaskFragments);
focusedWindowAdded |= windowState.isFocused();
} else if (isUntouchableNavigationBar(windowState, mTempRegion1)) {
// If this widow is navigation bar without touchable region, accounting the
@@ -1726,7 +1806,7 @@ final class AccessibilityController {
private boolean windowMattersToAccessibility(WindowState windowState,
Region regionInScreen, Region unaccountedSpace,
- HashSet<Integer> skipRemainingWindowsForTasks) {
+ ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
final RecentsAnimationController controller = mService.getRecentsAnimationController();
if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
return false;
@@ -1737,8 +1817,9 @@ final class AccessibilityController {
}
// If the window is part of a task that we're finished with - ignore.
- final Task task = windowState.getTask();
- if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) {
+ final TaskFragment taskFragment = windowState.getTaskFragment();
+ if (taskFragment != null
+ && skipRemainingWindowsForTaskFragments.contains(taskFragment)) {
return false;
}
@@ -1764,7 +1845,8 @@ final class AccessibilityController {
}
private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen,
- Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
+ Region unaccountedSpace,
+ ArrayList<TaskFragment> skipRemainingWindowsForTaskFragments) {
if (windowState.mAttrs.type
!= WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
@@ -1794,11 +1876,11 @@ final class AccessibilityController {
Region.Op.REVERSE_DIFFERENCE);
}
- final Task task = windowState.getTask();
- if (task != null) {
+ final TaskFragment taskFragment = windowState.getTaskFragment();
+ if (taskFragment != null) {
// If the window is associated with a particular task, we can skip the
// rest of the windows for that task.
- skipRemainingWindowsForTasks.add(task.mTaskId);
+ skipRemainingWindowsForTaskFragments.add(taskFragment);
} else if (!windowState.hasTapExcludeRegion()) {
// If the window is not associated with a particular task, then it is
// globally modal. In this case we can skip all remaining windows when
@@ -1945,8 +2027,8 @@ final class AccessibilityController {
private static final class AccessibilityControllerInternalImpl
implements AccessibilityControllerInternal {
- private static AccessibilityControllerInternal sInstance;
- static AccessibilityControllerInternal getInstance(WindowManagerService service) {
+ private static AccessibilityControllerInternalImpl sInstance;
+ static AccessibilityControllerInternalImpl getInstance(WindowManagerService service) {
synchronized (STATIC_LOCK) {
if (sInstance == null) {
sInstance = new AccessibilityControllerInternalImpl(service);
@@ -1956,18 +2038,23 @@ final class AccessibilityController {
}
private final AccessibilityTracing mTracing;
+ private volatile long mEnabledTracingFlags;
+
private AccessibilityControllerInternalImpl(WindowManagerService service) {
mTracing = AccessibilityTracing.getInstance(service);
+ mEnabledTracingFlags = 0L;
}
@Override
- public void startTrace() {
+ public void startTrace(long loggingTypes) {
+ mEnabledTracingFlags = loggingTypes;
mTracing.startTrace();
}
@Override
public void stopTrace() {
mTracing.stopTrace();
+ mEnabledTracingFlags = 0L;
}
@Override
@@ -1975,19 +2062,37 @@ final class AccessibilityController {
return mTracing.isEnabled();
}
+ boolean isTracingEnabled(long flags) {
+ return (flags & mEnabledTracingFlags) != 0L;
+ }
+
+ void logTrace(String where, long loggingTypes) {
+ logTrace(where, loggingTypes, "");
+ }
+
+ void logTrace(String where, long loggingTypes, String callingParams) {
+ logTrace(where, loggingTypes, callingParams, "".getBytes(), Binder.getCallingUid());
+ }
+
+ void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid) {
+ mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid,
+ new HashSet<String>(Arrays.asList("logTrace")));
+ }
+
@Override
- public void logTrace(
- String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] stackTrace) {
- mTracing.logState(where, callingParams, a11yDump, callingUid, stackTrace);
+ public void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries) {
+ mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, stackTrace,
+ ignoreStackEntries);
}
@Override
- public void logTrace(
- String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] callStack, long timeStamp, int processId, long threadId) {
- mTracing.logState(where, callingParams, a11yDump, callingUid, callStack, timeStamp,
- processId, threadId);
+ public void logTrace(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid, StackTraceElement[] callStack, long timeStamp, int processId,
+ long threadId, Set<String> ignoreStackEntries) {
+ mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, callStack,
+ timeStamp, processId, threadId, ignoreStackEntries);
}
}
@@ -2003,8 +2108,7 @@ final class AccessibilityController {
}
private static final int BUFFER_CAPACITY = 1024 * 1024 * 12;
- private static final String TRACE_FILENAME = "/data/misc/a11ytrace/a11y_trace.pb";
- private static final String TRACE_DIRECTORY = "/data/misc/a11ytrace/";
+ private static final String TRACE_FILENAME = "/data/misc/a11ytrace/a11y_trace" + WINSCOPE_EXT;
private static final String TAG = "AccessibilityTracing";
private static final long MAGIC_NUMBER_VALUE =
((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
@@ -2034,13 +2138,6 @@ final class AccessibilityController {
return;
}
synchronized (mLock) {
- try {
- Files.createDirectories(Paths.get(TRACE_DIRECTORY));
- mTraceFile.createNewFile();
- } catch (Exception e) {
- Slog.e(TAG, "Error: Failed to create trace file.");
- return;
- }
mEnabled = true;
mBuffer.resetBuffer();
}
@@ -2071,106 +2168,150 @@ final class AccessibilityController {
/**
* Write an accessibility trace log entry.
*/
- void logState(String where) {
+ void logState(String where, long loggingTypes) {
if (!mEnabled) {
return;
}
- logState(where, "");
+ logState(where, loggingTypes, "");
}
/**
* Write an accessibility trace log entry.
*/
- void logState(String where, String callingParams) {
+ void logState(String where, long loggingTypes, String callingParams) {
if (!mEnabled) {
return;
}
- logState(where, callingParams, "".getBytes());
+ logState(where, loggingTypes, callingParams, "".getBytes());
}
/**
* Write an accessibility trace log entry.
*/
- void logState(String where, String callingParams, byte[] a11yDump) {
+ void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump) {
if (!mEnabled) {
return;
}
- logState(where, callingParams, a11yDump, Binder.getCallingUid());
+ logState(where, loggingTypes, callingParams, a11yDump, Binder.getCallingUid(),
+ new HashSet<String>(Arrays.asList("logState")));
}
/**
* Write an accessibility trace log entry.
*/
- void logState(
- String where, String callingParams, byte[] a11yDump, int callingUid) {
+ void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid, Set<String> ignoreStackEntries) {
if (!mEnabled) {
return;
}
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
-
- logState(where, callingParams, a11yDump, callingUid, stackTraceElements);
+ ignoreStackEntries.add("logState");
+ logState(where, loggingTypes, callingParams, a11yDump, callingUid, stackTraceElements,
+ ignoreStackEntries);
}
/**
* Write an accessibility trace log entry.
*/
- void logState(String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] stackTrace) {
+ void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries) {
if (!mEnabled) {
return;
}
-
- log(where, callingParams, a11yDump, callingUid, stackTrace,
+ log(where, loggingTypes, callingParams, a11yDump, callingUid, stackTrace,
SystemClock.elapsedRealtimeNanos(),
Process.myPid() + ":" + Application.getProcessName(),
- Thread.currentThread().getId() + ":" + Thread.currentThread().getName());
+ Thread.currentThread().getId() + ":" + Thread.currentThread().getName(),
+ ignoreStackEntries);
}
/**
* Write an accessibility trace log entry.
*/
- void logState(String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] callingStack, long timeStamp, int processId, long threadId) {
+ void logState(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid, StackTraceElement[] callingStack, long timeStamp, int processId,
+ long threadId, Set<String> ignoreStackEntries) {
if (!mEnabled) {
return;
}
- log(where, callingParams, a11yDump, callingUid, callingStack, timeStamp,
- String.valueOf(processId), String.valueOf(threadId));
+ log(where, loggingTypes, callingParams, a11yDump, callingUid, callingStack, timeStamp,
+ String.valueOf(processId), String.valueOf(threadId), ignoreStackEntries);
}
- private String toStackTraceString(StackTraceElement[] stackTraceElements) {
+ private String toStackTraceString(StackTraceElement[] stackTraceElements,
+ Set<String> ignoreStackEntries) {
+
if (stackTraceElements == null) {
return "";
}
+
StringBuilder stringBuilder = new StringBuilder();
- boolean skip = true;
- for (int i = 0; i < stackTraceElements.length; i++) {
- if (stackTraceElements[i].toString().contains(
- AccessibilityTracing.class.getSimpleName())) {
- skip = false;
- } else if (!skip) {
- stringBuilder.append(stackTraceElements[i].toString()).append("\n");
+ int i = 0;
+
+ // Skip the first a few elements until after any ignoreStackEntries
+ int firstMatch = -1;
+ while (i < stackTraceElements.length) {
+ for (String ele : ignoreStackEntries) {
+ if (stackTraceElements[i].toString().contains(ele)) {
+ // found the first stack element containing the ignorable stack entries
+ firstMatch = i;
+ break;
+ }
+ }
+ if (firstMatch < 0) {
+ // Haven't found the first match yet, continue
+ i++;
+ } else {
+ break;
+ }
+ }
+ int lastMatch = firstMatch;
+ if (i < stackTraceElements.length) {
+ i++;
+ // Found the first match. Now look for the last match.
+ while (i < stackTraceElements.length) {
+ for (String ele : ignoreStackEntries) {
+ if (stackTraceElements[i].toString().contains(ele)) {
+ // This is a match. Look at the next stack element.
+ lastMatch = i;
+ break;
+ }
+ }
+ if (lastMatch != i) {
+ // Found a no-match.
+ break;
+ }
+ i++;
}
}
+
+ i = lastMatch + 1;
+ while (i < stackTraceElements.length) {
+ stringBuilder.append(stackTraceElements[i].toString()).append("\n");
+ i++;
+ }
return stringBuilder.toString();
}
/**
* Write the current state to the buffer
*/
- private void log(String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] callingStack, long timeStamp, String processName,
- String threadName) {
+ private void log(String where, long loggingTypes, String callingParams, byte[] a11yDump,
+ int callingUid, StackTraceElement[] callingStack, long timeStamp,
+ String processName, String threadName, Set<String> ignoreStackEntries) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = timeStamp;
- args.arg2 = where;
- args.arg3 = processName;
- args.arg4 = threadName;
- args.arg5 = callingUid;
- args.arg6 = callingParams;
- args.arg7 = callingStack;
- args.arg8 = a11yDump;
- mHandler.obtainMessage(LogHandler.MESSAGE_LOG_TRACE_ENTRY, args).sendToTarget();
+ args.arg2 = loggingTypes;
+ args.arg3 = where;
+ args.arg4 = processName;
+ args.arg5 = threadName;
+ args.arg6 = ignoreStackEntries;
+ args.arg7 = callingParams;
+ args.arg8 = callingStack;
+ args.arg9 = a11yDump;
+
+ mHandler.obtainMessage(
+ LogHandler.MESSAGE_LOG_TRACE_ENTRY, callingUid, 0, args).sendToTarget();
}
/**
@@ -2199,8 +2340,6 @@ final class AccessibilityController {
LocalServices.getService(PackageManagerInternal.class);
long tokenOuter = os.start(ENTRY);
- String callingStack =
- toStackTraceString((StackTraceElement[]) args.arg7);
long reportedTimeStampNanos = (long) args.arg1;
long currentElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
@@ -2213,13 +2352,25 @@ final class AccessibilityController {
os.write(ELAPSED_REALTIME_NANOS, reportedTimeStampNanos);
os.write(CALENDAR_TIME, fm.format(reportedTimeMillis).toString());
- os.write(WHERE, (String) args.arg2);
- os.write(PROCESS_NAME, (String) args.arg3);
- os.write(THREAD_ID_NAME, (String) args.arg4);
- os.write(CALLING_PKG, pmInternal.getNameForUid((int) args.arg5));
- os.write(CALLING_PARAMS, (String) args.arg6);
+
+ long loggingTypes = (long) args.arg2;
+ List<String> loggingTypeNames =
+ AccessibilityTrace.getNamesOfLoggingTypes(loggingTypes);
+
+ for (String type : loggingTypeNames) {
+ os.write(LOGGING_TYPE, type);
+ }
+ os.write(WHERE, (String) args.arg3);
+ os.write(PROCESS_NAME, (String) args.arg4);
+ os.write(THREAD_ID_NAME, (String) args.arg5);
+ os.write(CALLING_PKG, pmInternal.getNameForUid(message.arg1));
+ os.write(CALLING_PARAMS, (String) args.arg7);
+
+ String callingStack = toStackTraceString(
+ (StackTraceElement[]) args.arg8, (Set<String>) args.arg6);
+
os.write(CALLING_STACKS, callingStack);
- os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg8);
+ os.write(ACCESSIBILITY_SERVICE, (byte[]) args.arg9);
long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index e02e8671f211..ee72fc8622a5 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -30,6 +30,11 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.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;
+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.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -37,10 +42,9 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -72,6 +76,7 @@ import android.service.voice.VoiceInteractionManagerInternal;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
import android.window.SizeConfigurationBuckets;
+import android.window.TransitionInfo;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -193,7 +198,7 @@ class ActivityClientController extends IActivityClientController.Stub {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
- if (r.attachedToProcess() && r.isState(Task.ActivityState.RESTARTING_PROCESS)) {
+ if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) {
// The activity was requested to restart from
// {@link #restartActivityProcessIfVisible}.
restartingName = r.app.mName;
@@ -540,6 +545,29 @@ class ActivityClientController extends IActivityClientController.Stub {
}
@Override
+ @Nullable
+ public IBinder getActivityTokenBelow(IBinder activityToken) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken);
+ if (ar == null) {
+ return null;
+ }
+ // Exclude finishing activity.
+ final ActivityRecord below = ar.getTask().getActivity((r) -> !r.finishing,
+ ar, false /*includeBoundary*/, true /*traverseTopToBottom*/);
+ if (below != null && below.getUid() == ar.getUid()) {
+ return below.appToken.asBinder();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return null;
+ }
+
+ @Override
public ComponentName getCallingActivity(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = getCallingRecord(token);
@@ -1040,10 +1068,14 @@ class ActivityClientController extends IActivityClientController.Stub {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
- if (r != null && r.isState(Task.ActivityState.RESUMED, Task.ActivityState.PAUSING)) {
+ if (r != null && r.isState(RESUMED, PAUSING)) {
r.mDisplayContent.mAppTransition.overridePendingAppTransition(
packageName, enterAnim, exitAnim, null, null,
r.mOverrideTaskTransition);
+ r.mTransitionController.setOverrideAnimation(
+ TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName,
+ enterAnim, exitAnim, r.mOverrideTaskTransition),
+ null /* startCallback */, null /* finishCallback */);
}
}
Binder.restoreCallingIdentity(origId);
@@ -1183,7 +1215,7 @@ class ActivityClientController extends IActivityClientController.Stub {
try {
final Intent baseActivityIntent;
final boolean launchedFromHome;
-
+ final boolean isLastRunningActivity;
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
@@ -1195,22 +1227,25 @@ class ActivityClientController extends IActivityClientController.Stub {
return;
}
- final Intent baseIntent = r.getTask().getBaseIntent();
- final boolean activityIsBaseActivity = baseIntent != null
- && r.mActivityComponent.equals(baseIntent.getComponent());
- baseActivityIntent = activityIsBaseActivity ? r.intent : null;
+ final Task task = r.getTask();
+ isLastRunningActivity = task.topRunningActivity() == r;
+
+ final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity);
+ baseActivityIntent = isBaseActivity ? r.intent : null;
+
launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
// If the activity is one of the main entry points for the application, then we should
// refrain from finishing the activity and instead move it to the back to keep it in
// memory. The requirements for this are:
- // 1. The current activity is the base activity for the task.
- // 2. a. If the activity was launched by the home process, we trust that its intent
+ // 1. The activity is the last running activity in the task.
+ // 2. The current activity is the base activity for the task.
+ // 3. a. If the activity was launched by the home process, we trust that its intent
// was resolved, so we check if the it is a main intent for the application.
// b. Otherwise, we query Package Manager to verify whether the activity is a
// launcher activity for the application.
- if (baseActivityIntent != null
+ if (baseActivityIntent != null && isLastRunningActivity
&& ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
|| isLauncherActivity(baseActivityIntent.getComponent()))) {
moveActivityTaskToBack(token, false /* nonRoot */);
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
new file mode 100644
index 000000000000..1c2333a6ffa4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -0,0 +1,98 @@
+/*
+ * 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.wm;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback to intercept activity starts and possibly block/redirect them.
+ */
+public abstract class ActivityInterceptorCallback {
+ /**
+ * Intercept the launch intent based on various signals. If an interception happened, returns
+ * a new/existing non-null {@link Intent} which may redirect to another activity.
+ *
+ * @return null if no interception occurred, or a non-null intent which replaces the
+ * existing intent.
+ */
+ public abstract @Nullable Intent intercept(ActivityInterceptorInfo info);
+
+ /**
+ * The unique id of each interceptor which determines the order it will execute in.
+ */
+ @IntDef(suffix = { "_ORDERED_ID" }, value = {
+ FIRST_ORDERED_ID,
+ LAST_ORDERED_ID // Update this when adding new ids
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OrderedId {}
+
+ /**
+ * The first id, used by the framework to determine the valid range of ids.
+ */
+ static final int FIRST_ORDERED_ID = 0;
+
+ /**
+ * The final id, used by the framework to determine the valid range of ids. Update this when
+ * adding new ids.
+ */
+ static final int LAST_ORDERED_ID = FIRST_ORDERED_ID;
+
+ /**
+ * Data class for storing the various arguments needed for activity interception.
+ */
+ public static final class ActivityInterceptorInfo {
+ public final int realCallingUid;
+ public final int realCallingPid;
+ public final int userId;
+ public final String callingPackage;
+ public final String callingFeatureId;
+ public final Intent intent;
+ public final ResolveInfo rInfo;
+ public final ActivityInfo aInfo;
+ public final String resolvedType;
+ public final int callingPid;
+ public final int callingUid;
+ public final ActivityOptions checkedOptions;
+
+ public ActivityInterceptorInfo(int realCallingUid, int realCallingPid, int userId,
+ String callingPackage, String callingFeatureId, Intent intent,
+ ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, int callingPid,
+ int callingUid, ActivityOptions checkedOptions) {
+ this.realCallingUid = realCallingUid;
+ this.realCallingPid = realCallingPid;
+ this.userId = userId;
+ this.callingPackage = callingPackage;
+ this.callingFeatureId = callingFeatureId;
+ this.intent = intent;
+ this.rInfo = rInfo;
+ this.aInfo = aInfo;
+ this.resolvedType = resolvedType;
+ this.callingPid = callingPid;
+ this.callingUid = callingUid;
+ this.checkedOptions = checkedOptions;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index afe6345f096f..e640650d8f12 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -60,6 +60,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.server.am.MemoryStatUtil.MemoryStat;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
@@ -91,6 +93,7 @@ import android.util.BoostFramework;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -159,6 +162,9 @@ class ActivityMetricsLogger {
private final ArrayList<TransitionInfo> mTransitionInfoList = new ArrayList<>();
/** Map : Last launched activity => {@link TransitionInfo} */
private final ArrayMap<ActivityRecord, TransitionInfo> mLastTransitionInfo = new ArrayMap<>();
+ /** SparseArray : Package UID => {@link PackageCompatStateInfo} */
+ private final SparseArray<PackageCompatStateInfo> mPackageUidToCompatStateInfo =
+ new SparseArray<>(0);
private ArtManagerInternal mArtManagerInternal;
private final StringBuilder mStringBuilder = new StringBuilder();
@@ -190,7 +196,11 @@ class ActivityMetricsLogger {
@VisibleForTesting
boolean allDrawn() {
- return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.allDrawn();
+ return mAssociatedTransitionInfo != null && mAssociatedTransitionInfo.mIsDrawn;
+ }
+
+ boolean hasActiveTransitionInfo() {
+ return mAssociatedTransitionInfo != null;
}
boolean contains(ActivityRecord r) {
@@ -220,8 +230,8 @@ class ActivityMetricsLogger {
final boolean mProcessRunning;
/** whether the process of the launching activity didn't have any active activity. */
final boolean mProcessSwitch;
- /** The activities that should be drawn. */
- final ArrayList<ActivityRecord> mPendingDrawActivities = new ArrayList<>(2);
+ /** Whether the last launched activity has reported drawn. */
+ boolean mIsDrawn;
/** The latest activity to have been launched. */
@NonNull ActivityRecord mLastLaunchedActivity;
@@ -306,15 +316,15 @@ class ActivityMetricsLogger {
return;
}
if (mLastLaunchedActivity != null) {
- // Transfer the launch cookie because it is a consecutive launch event.
+ // Transfer the launch cookie and launch root task because it is a consecutive
+ // launch event.
r.mLaunchCookie = mLastLaunchedActivity.mLaunchCookie;
mLastLaunchedActivity.mLaunchCookie = null;
+ r.mLaunchRootTask = mLastLaunchedActivity.mLaunchRootTask;
+ mLastLaunchedActivity.mLaunchRootTask = null;
}
mLastLaunchedActivity = r;
- if (!r.noDisplay && !r.isReportedDrawn()) {
- if (DEBUG_METRICS) Slog.i(TAG, "Add pending draw " + r);
- mPendingDrawActivities.add(r);
- }
+ mIsDrawn = r.isReportedDrawn();
}
/** Returns {@code true} if the incoming activity can belong to this transition. */
@@ -325,28 +335,7 @@ class ActivityMetricsLogger {
/** @return {@code true} if the activity matches a launched activity in this transition. */
boolean contains(ActivityRecord r) {
- return r != null && (r == mLastLaunchedActivity || mPendingDrawActivities.contains(r));
- }
-
- /** Called when the activity is drawn or won't be drawn. */
- void removePendingDrawActivity(ActivityRecord r) {
- if (DEBUG_METRICS) Slog.i(TAG, "Remove pending draw " + r);
- mPendingDrawActivities.remove(r);
- }
-
- boolean allDrawn() {
- return mPendingDrawActivities.isEmpty();
- }
-
- /** Only keep the records which can be drawn. */
- void updatePendingDraw() {
- for (int i = mPendingDrawActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = mPendingDrawActivities.get(i);
- if (!r.mVisibleRequested) {
- if (DEBUG_METRICS) Slog.i(TAG, "Discard pending draw " + r);
- mPendingDrawActivities.remove(i);
- }
- }
+ return r == mLastLaunchedActivity;
}
/**
@@ -369,7 +358,7 @@ class ActivityMetricsLogger {
@Override
public String toString() {
return "TransitionInfo{" + Integer.toHexString(System.identityHashCode(this))
- + " a=" + mLastLaunchedActivity + " ua=" + mPendingDrawActivities + "}";
+ + " a=" + mLastLaunchedActivity + " d=" + mIsDrawn + "}";
}
}
@@ -450,6 +439,15 @@ class ActivityMetricsLogger {
}
}
+ /** Information about the App Compat state logging associated with a package UID . */
+ private static final class PackageCompatStateInfo {
+ /** All activities that have a visible state. */
+ final ArrayList<ActivityRecord> mVisibleActivities = new ArrayList<>();
+ /** The last logged state. */
+ int mLastLoggedState = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+ @Nullable ActivityRecord mLastLoggedActivity;
+ }
+
ActivityMetricsLogger(ActivityTaskSupervisor supervisor, Looper looper) {
mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
mSupervisor = supervisor;
@@ -636,6 +634,7 @@ class ActivityMetricsLogger {
if (crossPackage) {
startLaunchTrace(info);
}
+ scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
return;
}
@@ -657,13 +656,7 @@ class ActivityMetricsLogger {
// As abort for no process switch.
launchObserverNotifyIntentFailed();
}
- if (launchedActivity.mDisplayContent.isSleeping()) {
- // It is unknown whether the activity can be drawn or not, e.g. it depends on the
- // keyguard states and the attributes or flags set by the activity. If the activity
- // keeps invisible in the grace period, the tracker will be cancelled so it won't get
- // a very long launch time that takes unlocking as the end of launch.
- scheduleCheckActivityToBeDrawn(launchedActivity, UNKNOWN_VISIBILITY_CHECK_DELAY_MS);
- }
+ scheduleCheckActivityToBeDrawnIfSleeping(launchedActivity);
// If the previous transitions are no longer visible, abort them to avoid counting the
// launch time when resuming from back stack. E.g. launch 2 independent tasks in a short
@@ -671,13 +664,22 @@ class ActivityMetricsLogger {
// visible such as after the top task is finished.
for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) {
final TransitionInfo prevInfo = mTransitionInfoList.get(i);
- prevInfo.updatePendingDraw();
- if (prevInfo.allDrawn()) {
+ if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) {
abort(prevInfo, "nothing will be drawn");
}
}
}
+ private void scheduleCheckActivityToBeDrawnIfSleeping(@NonNull ActivityRecord r) {
+ if (r.mDisplayContent.isSleeping()) {
+ // It is unknown whether the activity can be drawn or not, e.g. it depends on the
+ // keyguard states and the attributes or flags set by the activity. If the activity
+ // keeps invisible in the grace period, the tracker will be cancelled so it won't get
+ // a very long launch time that takes unlocking as the end of launch.
+ scheduleCheckActivityToBeDrawn(r, UNKNOWN_VISIBILITY_CHECK_DELAY_MS);
+ }
+ }
+
/**
* Notifies the tracker that all windows of the app have been drawn.
*
@@ -689,16 +691,16 @@ class ActivityMetricsLogger {
if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn " + r);
final TransitionInfo info = getActiveTransitionInfo(r);
- if (info == null || info.allDrawn()) {
- if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn no activity to be drawn");
+ if (info == null || info.mIsDrawn) {
+ if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn not pending drawn " + info);
return null;
}
// Always calculate the delay because the caller may need to know the individual drawn time.
info.mWindowsDrawnDelayMs = info.calculateDelay(timestampNs);
- info.removePendingDrawActivity(r);
+ info.mIsDrawn = true;
final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
- if (info.mLoggedTransitionStarting && info.allDrawn()) {
- done(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs);
+ if (info.mLoggedTransitionStarting) {
+ done(false /* abort */, info, "notifyWindowsDrawn", timestampNs);
}
if (r.mWmService.isRecentsAnimationTarget(r)) {
r.mWmService.getRecentsAnimationController().logRecentsAnimationStartTime(
@@ -747,10 +749,8 @@ class ActivityMetricsLogger {
info.mCurrentTransitionDelayMs = info.calculateDelay(timestampNs);
info.mReason = activityToReason.valueAt(index);
info.mLoggedTransitionStarting = true;
- info.updatePendingDraw();
- if (info.allDrawn()) {
- done(false /* abort */, info, "notifyTransitionStarting - all windows drawn",
- timestampNs);
+ if (info.mIsDrawn) {
+ done(false /* abort */, info, "notifyTransitionStarting drawn", timestampNs);
}
}
}
@@ -765,6 +765,17 @@ class ActivityMetricsLogger {
/** Makes sure that the reference to the removed activity is cleared. */
void notifyActivityRemoved(@NonNull ActivityRecord r) {
mLastTransitionInfo.remove(r);
+
+ final int packageUid = r.info.applicationInfo.uid;
+ final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+ if (compatStateInfo == null) {
+ return;
+ }
+
+ compatStateInfo.mVisibleActivities.remove(r);
+ if (compatStateInfo.mLastLoggedActivity == r) {
+ compatStateInfo.mLastLoggedActivity = null;
+ }
}
/**
@@ -781,19 +792,16 @@ class ActivityMetricsLogger {
Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
+ " state=" + r.getState() + " finishing=" + r.finishing);
}
- if (r.isState(Task.ActivityState.RESUMED) && r.mDisplayContent.isSleeping()) {
+ if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
// The activity may be launching while keyguard is locked. The keyguard may be dismissed
// after the activity finished relayout, so skip the visibility check to avoid aborting
// the tracking of launch event.
return;
}
if (!r.mVisibleRequested || r.finishing) {
- info.removePendingDrawActivity(r);
- if (info.mLastLaunchedActivity == r) {
- // Check if the tracker can be cancelled because the last launched activity may be
- // no longer visible.
- scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
- }
+ // Check if the tracker can be cancelled because the last launched activity may be
+ // no longer visible.
+ scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
}
}
@@ -812,17 +820,12 @@ class ActivityMetricsLogger {
// If we have an active transition that's waiting on a certain activity that will be
// invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
- // We have no active transitions.
+ // We have no active transitions. Or the notified activity whose visibility changed is
+ // no longer the launched activity, then we can still wait to get onWindowsDrawn.
if (info == null) {
return;
}
- // The notified activity whose visibility changed is no longer the launched activity.
- // We can still wait to get onWindowsDrawn.
- if (info.mLastLaunchedActivity != r) {
- return;
- }
-
// If the task of the launched activity contains any activity to be drawn, then the
// window drawn event should report later to complete the transition. Otherwise all
// activities in this task may be finished, invisible or drawn, so the transition event
@@ -905,7 +908,6 @@ class ActivityMetricsLogger {
}
logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
}
- info.mPendingDrawActivities.clear();
mTransitionInfoList.remove(info);
}
@@ -1118,7 +1120,7 @@ class ActivityMetricsLogger {
if (info == null) {
return null;
}
- if (!info.allDrawn() && info.mPendingFullyDrawn == null) {
+ if (!info.mIsDrawn && info.mPendingFullyDrawn == null) {
// There are still undrawn activities, postpone reporting fully drawn until all of its
// windows are drawn. So that is closer to an usable state.
info.mPendingFullyDrawn = () -> {
@@ -1297,6 +1299,126 @@ class ActivityMetricsLogger {
memoryStat.swapInBytes);
}
+ /**
+ * Logs the current App Compat state of the given {@link ActivityRecord} with its package
+ * UID, if all of the following hold:
+ * <ul>
+ * <li>The current state is different than the last logged state for the package UID of the
+ * activity.
+ * <li>If the current state is NOT_VISIBLE, there is a previously logged state for the
+ * package UID and there are no other visible activities with the same package UID.
+ * <li>The last logged activity with the same package UID is either {@code activity} (or an
+ * activity that has been removed) or the last logged state is NOT_VISIBLE or NOT_LETTERBOXED.
+ * </ul>
+ *
+ * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code
+ * activity} (or an activity that has been removed) wasn't, looks for the first visible activity
+ * with the same package UID that has a letterboxed state, or a non-letterboxed state if
+ * there isn't one, and logs that state.
+ *
+ * <p>This method assumes that the caller is wrapping the call with a synchronized block so
+ * that there won't be a race condition between two activities with the same package.
+ */
+ void logAppCompatState(@NonNull ActivityRecord activity) {
+ final int packageUid = activity.info.applicationInfo.uid;
+ final int state = activity.getAppCompatState();
+
+ if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
+ mPackageUidToCompatStateInfo.put(packageUid, new PackageCompatStateInfo());
+ }
+ final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+ final int lastLoggedState = compatStateInfo.mLastLoggedState;
+ final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;
+
+ final boolean isVisible = state != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+ final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities;
+ if (isVisible && !visibleActivities.contains(activity)) {
+ visibleActivities.add(activity);
+ } else if (!isVisible) {
+ visibleActivities.remove(activity);
+ if (visibleActivities.isEmpty()) {
+ // No need to keep the entry if there are no visible activities.
+ mPackageUidToCompatStateInfo.remove(packageUid);
+ }
+ }
+
+ if (state == lastLoggedState) {
+ // We don’t want to log the same state twice or log DEFAULT_NOT_VISIBLE before any
+ // visible state was logged.
+ return;
+ }
+
+ if (!isVisible && !visibleActivities.isEmpty()) {
+ // There is another visible activity for this package UID.
+ if (lastLoggedActivity == null || activity == lastLoggedActivity) {
+ // Make sure a new visible state is logged if needed.
+ findAppCompatStateToLog(compatStateInfo, packageUid);
+ }
+ return;
+ }
+
+ if (lastLoggedActivity != null && activity != lastLoggedActivity
+ && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE
+ && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) {
+ // Another visible activity for this package UID has logged a letterboxed state.
+ return;
+ }
+
+ logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
+ }
+
+ /**
+ * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed
+ * state, or a non-letterboxed state if there isn't one, and logs that state for the given
+ * {@code packageUid}.
+ *
+ * <p>If there is a visible activity in {@code compatStateInfo} with the same state as the
+ * last logged state for the given {@code packageUid}, changes the last logged activity to
+ * reference the first such activity without actually logging the same state twice.
+ */
+ private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) {
+ final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities;
+ final int lastLoggedState = compatStateInfo.mLastLoggedState;
+
+ ActivityRecord activityToLog = null;
+ int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+ for (int i = 0; i < visibleActivities.size(); i++) {
+ ActivityRecord activity = visibleActivities.get(i);
+ int state = activity.getAppCompatState();
+ if (state == lastLoggedState) {
+ // Change last logged activity without logging the same state twice.
+ compatStateInfo.mLastLoggedActivity = activity;
+ return;
+ }
+ if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
+ // This shouldn't happen.
+ Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: "
+ + packageUid);
+ continue;
+ }
+ if (stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE || (
+ stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED
+ && state != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED)) {
+ activityToLog = activity;
+ stateToLog = state;
+ }
+ }
+ if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
+ logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
+ }
+ }
+
+ private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
+ int packageUid, PackageCompatStateInfo compatStateInfo) {
+ compatStateInfo.mLastLoggedState = state;
+ compatStateInfo.mLastLoggedActivity = activity;
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);
+
+ if (DEBUG_METRICS) {
+ Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
+ }
+ }
+
private ArtManagerInternal getArtManagerInternal() {
if (mArtManagerInternal == null) {
// Note that this may be null.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 319d97869461..7f3c733ec33e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -46,8 +46,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
-import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO;
-import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -127,8 +125,24 @@ 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.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;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
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.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+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;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN;
import static com.android.server.wm.ActivityRecordProto.APP_STOPPED;
import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE;
@@ -140,11 +154,13 @@ import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING;
import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START;
import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN;
import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING;
+import static com.android.server.wm.ActivityRecordProto.MIN_ASPECT_RATIO;
import static com.android.server.wm.ActivityRecordProto.NAME;
import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS;
import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS;
import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED;
import static com.android.server.wm.ActivityRecordProto.PROC_ID;
+import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
@@ -192,18 +208,7 @@ import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.INITIALIZING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESTARTING_PROCESS;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
import static com.android.server.wm.TaskPersister.DEBUG;
import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -219,7 +224,6 @@ import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
@@ -259,6 +263,7 @@ import android.content.Intent;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ConstrainDisplayApisConfig;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -274,13 +279,13 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.storage.StorageManager;
import android.service.contentcapture.ActivityEvent;
import android.service.dreams.DreamActivity;
import android.service.dreams.DreamManagerInternal;
@@ -305,6 +310,7 @@ import android.view.InputApplicationHandle;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
+import android.view.Surface.Rotation;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
@@ -312,24 +318,24 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.SizeConfigurationBuckets;
import android.window.SplashScreen;
import android.window.SplashScreenView;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.TaskSnapshot;
+import android.window.TransitionInfo.AnimationOptions;
import android.window.WindowContainerToken;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.TransferPipe;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledFunction;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.am.AppTimeTracker;
import com.android.server.am.PendingIntentRecord;
@@ -340,7 +346,6 @@ import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriPermissionOwner;
import com.android.server.wm.ActivityMetricsLogger.TransitionInfoSnapshot;
import com.android.server.wm.SurfaceAnimator.AnimationType;
-import com.android.server.wm.Task.ActivityState;
import com.android.server.wm.WindowManagerService.H;
import com.android.server.wm.utils.InsetUtils;
@@ -349,6 +354,7 @@ import com.google.android.collect.Sets;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -402,10 +408,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1;
private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2;
- /**
- * Value to increment the z-layer when boosting a layer during animations. BOOST in l33tsp34k.
- */
- @VisibleForTesting static final int Z_BOOST_BASE = 800570000;
static final int INVALID_PID = -1;
// How long we wait until giving up on the last activity to pause. This
@@ -491,13 +493,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
private ActivityOptions mPendingOptions;
/** Non-null if {@link #mPendingOptions} specifies the remote animation. */
private RemoteAnimationAdapter mPendingRemoteAnimation;
- private IRemoteTransition mPendingRemoteTransition;
+ private RemoteTransition mPendingRemoteTransition;
ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
ActivityServiceConnectionsHolder mServiceConnectionsHolder; // Service connections.
UriPermissionOwner uriPermissions; // current special URI access perms.
WindowProcessController app; // if non-null, hosting application
- private ActivityState mState; // current state we are in
+ private State mState; // current state we are in
private Bundle mIcicle; // last saved activity state
private PersistableBundle mPersistentState; // last persistently saved activity state
private boolean mHaveState = true; // Indicates whether the last saved state of activity is
@@ -549,11 +551,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
final ActivityTaskSupervisor mTaskSupervisor;
final RootWindowContainer mRootWindowContainer;
- static final int STARTING_WINDOW_NOT_SHOWN = 0;
- static final int STARTING_WINDOW_SHOWN = 1;
- static final int STARTING_WINDOW_REMOVED = 2;
- int mStartingWindowState = STARTING_WINDOW_NOT_SHOWN;
-
// Tracking splash screen status from previous activity
boolean mSplashScreenStyleEmpty = false;
@@ -561,6 +558,21 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
static final int LAUNCH_SOURCE_TYPE_HOME = 2;
static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3;
static final int LAUNCH_SOURCE_TYPE_APPLICATION = 4;
+
+ enum State {
+ INITIALIZING,
+ STARTED,
+ RESUMED,
+ PAUSING,
+ PAUSED,
+ STOPPING,
+ STOPPED,
+ FINISHING,
+ DESTROYING,
+ DESTROYED,
+ RESTARTING_PROCESS
+ }
+
/**
* The type of launch source.
*/
@@ -595,6 +607,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
*/
private CompatDisplayInsets mCompatDisplayInsets;
+ private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
+
boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session
IVoiceInteractionSession voiceSession; // Voice interaction session for this activity
@@ -660,6 +674,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
boolean allDrawn;
private boolean mLastAllDrawn;
+ /**
+ * Solely for reporting to ActivityMetricsLogger. Just tracks whether, the last time this
+ * Actiivty was part of a syncset, all windows were ready by the time the sync was ready (vs.
+ * only the top-occluding ones). The assumption here is if some were not ready, they were
+ * covered with starting-window/splash-screen.
+ */
+ boolean mLastAllReadyAtSync = false;
+
private boolean mLastContainsShowWhenLockedWindow;
private boolean mLastContainsDismissKeyguardWindow;
private boolean mLastContainsTurnScreenOnWindow;
@@ -717,7 +739,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// TODO: rename to mNoDisplay
@VisibleForTesting
boolean noDisplay;
- boolean mShowForAllUsers;
+ final boolean mShowForAllUsers;
// TODO: Make this final
int mTargetSdk;
@@ -740,6 +762,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
boolean startingDisplayed;
boolean startingMoved;
+ /**
+ * If it is non-null, it requires all activities who have the same starting data to be drawn
+ * to remove the starting window.
+ * TODO(b/189385912): Remove starting window related fields after migrating them to task.
+ */
+ private StartingData mSharedStartingData;
+
boolean mHandleExitSplashScreen;
@TransferSplashScreenState
int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -809,6 +838,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
*/
private final Configuration mTmpConfig = new Configuration();
private final Rect mTmpBounds = new Rect();
+ private final Rect mTmpOutNonDecorBounds = new Rect();
// Token for targeting this activity for assist purposes.
final Binder assistToken = new Binder();
@@ -820,6 +850,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Tracking cookie for the launch of this activity and it's task.
IBinder mLaunchCookie;
+ // Tracking indicated launch root in order to propagate it among trampoline activities.
+ WindowContainerToken mLaunchRootTask;
+
// Entering PiP is usually done in two phases, we put the task into pinned mode first and
// SystemUi sets the pinned mode on activity after transition is done.
boolean mWaitForEnteringPinnedMode;
@@ -874,19 +907,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
};
- private static String startingWindowStateToString(int state) {
- switch (state) {
- case STARTING_WINDOW_NOT_SHOWN:
- return "STARTING_WINDOW_NOT_SHOWN";
- case STARTING_WINDOW_SHOWN:
- return "STARTING_WINDOW_SHOWN";
- case STARTING_WINDOW_REMOVED:
- return "STARTING_WINDOW_REMOVED";
- default:
- return "unknown state=" + state;
- }
- }
-
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
final long now = SystemClock.uptimeMillis();
@@ -1011,7 +1031,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pw.println(mPendingRemoteAnimation.getCallingPid());
}
if (mPendingRemoteTransition != null) {
- pw.print(prefix + " pendingRemoteTransition=" + mPendingRemoteTransition);
+ pw.print(prefix + " pendingRemoteTransition="
+ + mPendingRemoteTransition.getRemoteTransition());
}
if (appTimeTracker != null) {
appTimeTracker.dumpWithHeader(pw, prefix, false);
@@ -1030,6 +1051,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pw.print("launchCookie=");
pw.println(mLaunchCookie);
}
+ if (mLaunchRootTask != null) {
+ pw.print(prefix);
+ pw.print("mLaunchRootTask=");
+ pw.println(mLaunchRootTask);
+ }
pw.print(prefix); pw.print("mHaveState="); pw.print(mHaveState);
pw.print(" mIcicle="); pw.println(mIcicle);
pw.print(prefix); pw.print("state="); pw.print(mState);
@@ -1038,9 +1064,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pw.print(" finishing="); pw.println(finishing);
pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused);
pw.print(" inHistory="); pw.print(inHistory);
- pw.print(" idle="); pw.print(idle);
- pw.print(" mStartingWindowState=");
- pw.println(startingWindowStateToString(mStartingWindowState));
+ pw.print(" idle="); pw.println(idle);
pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent());
pw.print(" noDisplay="); pw.print(noDisplay);
pw.print(" immersive="); pw.print(immersive);
@@ -1049,6 +1073,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pw.print(" forceNewConfig="); pw.println(forceNewConfig);
pw.print(prefix); pw.print("mActivityType=");
pw.println(activityTypeToString(getActivityType()));
+ pw.print(prefix); pw.print("mImeInsetsFrozenUntilStartInput=");
+ pw.println(mImeInsetsFrozenUntilStartInput);
if (requestedVrComponent != null) {
pw.print(prefix);
pw.print("requestedVrComponent=");
@@ -1085,6 +1111,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
pw.print(" mIsExiting="); pw.println(mIsExiting);
}
+ if (mSharedStartingData != null) {
+ pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+ }
if (mStartingWindow != null || mStartingSurface != null
|| startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
@@ -1135,10 +1164,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (info.getMaxAspectRatio() != 0) {
pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio());
}
- if (info.getMinAspectRatio() != 0) {
- pw.println(prefix + "minAspectRatio=" + info.getMinAspectRatio());
+ final float minAspectRatio = getMinAspectRatio();
+ if (minAspectRatio != 0) {
+ pw.println(prefix + "minAspectRatio=" + minAspectRatio);
}
- if (info.getMinAspectRatio() != info.getManifestMinAspectRatio()) {
+ if (minAspectRatio != info.getManifestMinAspectRatio()) {
// Log the fact that we've overridden the min aspect ratio from the manifest
pw.println(prefix + "manifestMinAspectRatio="
+ info.getManifestMinAspectRatio());
@@ -1148,8 +1178,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (info.configChanges != 0) {
pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
}
- pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis());
- pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis());
+ pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis(
+ sConstrainDisplayApisConfig));
+ pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis(
+ sConstrainDisplayApisConfig));
}
if (mLastParentBeforePip != null) {
pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
@@ -1158,6 +1190,76 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
mLetterboxUiController.dump(pw, prefix);
}
+ static boolean dumpActivity(FileDescriptor fd, PrintWriter pw, int index, ActivityRecord r,
+ String prefix, String label, boolean complete, boolean brief, boolean client,
+ String dumpPackage, boolean needNL, Runnable header, Task lastTask) {
+ if (dumpPackage != null && !dumpPackage.equals(r.packageName)) {
+ return false;
+ }
+
+ final boolean full = !brief && (complete || !r.isInHistory());
+ if (needNL) {
+ pw.println("");
+ }
+ if (header != null) {
+ header.run();
+ }
+
+ String innerPrefix = prefix + " ";
+ String[] args = new String[0];
+ if (lastTask != r.getTask()) {
+ lastTask = r.getTask();
+ pw.print(prefix);
+ pw.print(full ? "* " : " ");
+ pw.println(lastTask);
+ if (full) {
+ lastTask.dump(pw, prefix + " ");
+ } else if (complete) {
+ // Complete + brief == give a summary. Isn't that obvious?!?
+ if (lastTask.intent != null) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.println(lastTask.intent.toInsecureString());
+ }
+ }
+ }
+ pw.print(prefix); pw.print(full ? "* " : " "); pw.print(label);
+ pw.print(" #"); pw.print(index); pw.print(": ");
+ pw.println(r);
+ if (full) {
+ r.dump(pw, innerPrefix, true /* dumpAll */);
+ } else if (complete) {
+ // Complete + brief == give a summary. Isn't that obvious?!?
+ pw.print(innerPrefix);
+ pw.println(r.intent.toInsecureString());
+ if (r.app != null) {
+ pw.print(innerPrefix);
+ pw.println(r.app);
+ }
+ }
+ if (client && r.attachedToProcess()) {
+ // flush anything that is already in the PrintWriter since the thread is going
+ // to write to the file descriptor directly
+ pw.flush();
+ try {
+ TransferPipe tp = new TransferPipe();
+ try {
+ r.app.getThread().dumpActivity(
+ tp.getWriteFd(), r.appToken, innerPrefix, args);
+ // Short timeout, since blocking here can deadlock with the application.
+ tp.go(fd, 2000);
+ } finally {
+ tp.kill();
+ }
+ } catch (IOException e) {
+ pw.println(innerPrefix + "Failure while dumping the activity: " + e);
+ } catch (RemoteException e) {
+ pw.println(innerPrefix + "Got a RemoteException while dumping the activity");
+ }
+ }
+ return true;
+ }
+
void setAppTimeTracker(AppTimeTracker att) {
appTimeTracker = att;
}
@@ -1267,15 +1369,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
updatePictureInPictureMode(null, false);
} else {
mLastReportedMultiWindowMode = inMultiWindowMode;
- computeConfigurationAfterMultiWindowModeChange();
- // If the activity is in stopping or stopped state, for instance, it's in the
- // split screen task and not the top one, the last configuration it should keep
- // is the one before multi-window mode change.
- final ActivityState state = getState();
- if (state != STOPPED && state != STOPPING) {
- ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
- true /* ignoreVisibility */);
- }
+ ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
+ false /* ignoreVisibility */);
}
}
}
@@ -1294,33 +1389,46 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// precede the configuration change from the resize.
mLastReportedPictureInPictureMode = inPictureInPictureMode;
mLastReportedMultiWindowMode = inPictureInPictureMode;
- if (targetRootTaskBounds != null && !targetRootTaskBounds.isEmpty()) {
- computeConfigurationAfterMultiWindowModeChange();
- }
ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
true /* ignoreVisibility */);
}
}
- private void computeConfigurationAfterMultiWindowModeChange() {
- final Configuration newConfig = new Configuration();
- newConfig.setTo(task.getRequestedOverrideConfiguration());
- Rect outBounds = newConfig.windowConfiguration.getBounds();
- final Configuration parentConfig = task.getParent().getConfiguration();
- task.adjustForMinimalTaskDimensions(outBounds, outBounds, parentConfig);
- task.computeConfigResourceOverrides(newConfig, parentConfig);
- }
-
Task getTask() {
return task;
}
+ @Nullable
+ TaskFragment getTaskFragment() {
+ WindowContainer parent = getParent();
+ return parent != null ? parent.asTaskFragment() : null;
+ }
+
+ /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */
+ private boolean shouldStartChangeTransition(
+ @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) {
+ if (newParent == null || oldParent == null || !canStartChangeTransition()) {
+ return false;
+ }
+
+ // Transition change for the activity moving into a TaskFragment of different bounds.
+ return newParent.isOrganizedTaskFragment()
+ && !newParent.getBounds().equals(oldParent.getBounds());
+ }
+
@Override
- void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- final Task oldTask = oldParent != null ? (Task) oldParent : null;
- final Task newTask = newParent != null ? (Task) newParent : null;
+ void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) {
+ final TaskFragment oldParent = (TaskFragment) rawOldParent;
+ final TaskFragment newParent = (TaskFragment) rawNewParent;
+ final Task oldTask = oldParent != null ? oldParent.getTask() : null;
+ final Task newTask = newParent != null ? newParent.getTask() : null;
this.task = newTask;
+ if (shouldStartChangeTransition(newParent, oldParent)) {
+ // Animate change transition on TaskFragment level to get the correct window crop.
+ newParent.initializeChangeTransition(getBounds(), getSurfaceControl());
+ }
+
super.onParentChanged(newParent, oldParent);
if (isPersistable()) {
@@ -1376,11 +1484,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
updateColorTransform();
- if (oldTask != null) {
- oldTask.cleanUpActivityReferences(this);
+ if (oldParent != null) {
+ oldParent.cleanUpActivityReferences(this);
}
- if (newTask != null && isState(RESUMED)) {
- newTask.setResumedActivity(this, "onParentChanged");
+
+ if (newParent != null && isState(RESUMED)) {
+ newParent.setResumedActivity(this, "onParentChanged");
+ if (mStartingWindow != null && mStartingData != null
+ && mStartingData.mAssociatedTask == null && newParent.isEmbedded()) {
+ // The starting window should keep covering its task when the activity is
+ // reparented to a task fragment that may not fill the task bounds.
+ associateStartingDataWithTask();
+ attachStartingSurfaceToAssociatedTask();
+ }
mImeInsetsFrozenUntilStartInput = false;
}
@@ -1429,7 +1545,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
// TODO(b/169035022): move to a more-appropriate place.
- mAtmService.getTransitionController().collect(this);
+ mTransitionController.collect(this);
if (prevDc.mOpeningApps.remove(this)) {
// Transfer opening transition to new display.
mDisplayContent.mOpeningApps.add(this);
@@ -1674,6 +1790,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
info.windowLayout.windowLayoutAffinity =
uid + ":" + info.windowLayout.windowLayoutAffinity;
}
+ // Initialize once, when we know all system services are available.
+ if (sConstrainDisplayApisConfig == null) {
+ sConstrainDisplayApisConfig = new ConstrainDisplayApisConfig();
+ }
stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0;
nonLocalizedLabel = aInfo.nonLocalizedLabel;
labelRes = aInfo.labelRes;
@@ -1724,6 +1844,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
mHandoverLaunchDisplayId = options.getLaunchDisplayId();
mLaunchCookie = options.getLaunchCookie();
+ mLaunchRootTask = options.getLaunchRootTask();
}
mPersistentState = persistentState;
@@ -1808,6 +1929,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
task.setRootProcess(proc);
}
proc.addActivityIfNeeded(this);
+
+ // Update the associated task fragment after setting the process, since it's required for
+ // filtering to only report activities that belong to the same process.
+ final TaskFragment tf = getTaskFragment();
+ if (tf != null) {
+ tf.sendTaskFragmentInfoChanged();
+ }
}
boolean hasProcess() {
@@ -1938,9 +2066,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
+ @VisibleForTesting
boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo,
CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
- IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
+ ActivityRecord from, boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty) {
// If the display is frozen, we won't do anything until the actual window is
// displayed so there is no reason to put in the starting window.
@@ -1999,7 +2128,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
applyStartingWindowTheme(pkg, resolvedTheme);
- if (transferStartingWindow(transferFrom)) {
+ if (from != null && transferStartingWindow(from)) {
return true;
}
@@ -2024,6 +2153,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData");
mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams);
+ if (task.forAllLeafTaskFragments(TaskFragment::isEmbedded)) {
+ // Associate with the task so if this activity is resized by task fragment later, the
+ // starting window can keep the same bounds as the task.
+ associateStartingDataWithTask();
+ }
scheduleAddStartingWindow();
return true;
}
@@ -2217,7 +2351,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// unable to copy from shell, maybe it's not a splash screen. or something went wrong.
// either way, abort and reset the sequence.
if (parcelable == null
- || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING) {
+ || mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
+ || mStartingWindow == null) {
if (parcelable != null) {
parcelable.clearIfNeeded();
}
@@ -2226,13 +2361,17 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return;
}
// schedule attach splashScreen to client
+ final SurfaceControl windowAnimationLeash = TaskOrganizerController
+ .applyStartingWindowAnimation(mStartingWindow);
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- TransferSplashScreenViewStateItem.obtain(ATTACH_TO, parcelable));
+ TransferSplashScreenViewStateItem.obtain(parcelable,
+ windowAnimationLeash));
scheduleTransferSplashScreenTimeout();
} catch (Exception e) {
Slog.w(TAG, "onCopySplashScreenComplete fail: " + this);
+ mStartingWindow.cancelAnimation();
parcelable.clearIfNeeded();
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
}
@@ -2242,14 +2381,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
removeTransferSplashScreenTimeout();
// Client has draw the splash screen, so we can remove the starting window.
if (mStartingWindow != null) {
+ mStartingWindow.cancelAnimation();
mStartingWindow.hide(false, false);
}
- try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- TransferSplashScreenViewStateItem.obtain(HANDOVER_TO, null));
- } catch (Exception e) {
- Slog.w(TAG, "onSplashScreenAttachComplete fail: " + this);
- }
// no matter what, remove the starting window.
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
removeStartingWindowAnimation(false /* prepareAnimation */);
@@ -2272,15 +2406,44 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
- void removeStartingWindow() {
- removeStartingWindowAnimation(true /* prepareAnimation */);
+ void attachStartingWindow(@NonNull WindowState startingWindow) {
+ startingWindow.mStartingData = mStartingData;
+ mStartingWindow = startingWindow;
+ if (mStartingData != null && mStartingData.mAssociatedTask != null) {
+ attachStartingSurfaceToAssociatedTask();
+ }
}
- void removeStartingWindowAnimation(boolean prepareAnimation) {
+ private void attachStartingSurfaceToAssociatedTask() {
+ // Associate the configuration of starting window with the task.
+ overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
+ getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
+ mStartingData.mAssociatedTask.mSurfaceControl);
+ }
+
+ private void associateStartingDataWithTask() {
+ mStartingData.mAssociatedTask = task;
+ task.forAllActivities(r -> {
+ if (r.mVisibleRequested && !r.firstWindowDrawn) {
+ r.mSharedStartingData = mStartingData;
+ }
+ });
+ }
+
+ void removeStartingWindow() {
if (transferSplashScreenIfNeeded()) {
return;
}
+ removeStartingWindowAnimation(true /* prepareAnimation */);
+ }
+
+ void removeStartingWindowAnimation(boolean prepareAnimation) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
+ if (mSharedStartingData != null) {
+ mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
+ r.mSharedStartingData = null;
+ });
+ }
if (mStartingWindow == null) {
if (mStartingData != null) {
// Starting window has not been added yet, but it is scheduled to be added.
@@ -2337,38 +2500,25 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
- private void removeAppTokenFromDisplay() {
- if (mWmService.mRoot == null) return;
-
- final DisplayContent dc = mWmService.mRoot.getDisplayContent(getDisplayId());
- if (dc == null) {
- Slog.w(TAG, "removeAppTokenFromDisplay: Attempted to remove token: "
- + appToken + " from non-existing displayId=" + getDisplayId());
- return;
- }
- // Resume key dispatching if it is currently paused before we remove the container.
- resumeKeyDispatchingLocked();
- dc.removeAppToken(appToken.asBinder());
- }
-
/**
- * Reparents this activity into {@param newTask} at the provided {@param position}. The caller
- * should ensure that the {@param newTask} is not already the parent of this activity.
+ * Reparents this activity into {@param newTaskFrag} at the provided {@param position}. The
+ * caller should ensure that the {@param newTaskFrag} is not already the parent of this
+ * activity.
*/
- void reparent(Task newTask, int position, String reason) {
+ void reparent(TaskFragment newTaskFrag, int position, String reason) {
if (getParent() == null) {
Slog.w(TAG, "reparent: Attempted to reparent non-existing app token: " + appToken);
return;
}
- final Task prevTask = task;
- if (prevTask == newTask) {
- throw new IllegalArgumentException(reason + ": task=" + newTask
+ final TaskFragment prevTaskFrag = getTaskFragment();
+ if (prevTaskFrag == newTaskFrag) {
+ throw new IllegalArgumentException(reason + ": task fragment =" + newTaskFrag
+ " is already the parent of r=" + this);
}
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "reparent: moving activity=%s"
- + " to task=%d at %d", this, task.mTaskId, position);
- reparent(newTask, position);
+ + " to new task fragment in task=%d at %d", this, task.mTaskId, position);
+ reparent(newTaskFrag, position);
}
private boolean isHomeIntent(Intent intent) {
@@ -2489,6 +2639,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return task != null ? task.getOrganizedTask() : null;
}
+ /** Returns the organized parent {@link TaskFragment}. */
+ @Nullable
+ TaskFragment getOrganizedTaskFragment() {
+ final TaskFragment parent = getTaskFragment();
+ return parent != null ? parent.getOrganizedTaskFragment() : null;
+ }
+
+ @Override
+ boolean isEmbedded() {
+ final TaskFragment parent = getTaskFragment();
+ return parent != null && parent.isEmbedded();
+ }
+
@Override
@Nullable
TaskDisplayArea getDisplayArea() {
@@ -2578,9 +2741,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
boolean isResizeable() {
+ return isResizeable(/* checkPictureInPictureSupport */ true);
+ }
+
+ boolean isResizeable(boolean checkPictureInPictureSupport) {
return mAtmService.mForceResizableActivities
|| ActivityInfo.isResizeableMode(info.resizeMode)
- || info.supportsPictureInPicture();
+ || (info.supportsPictureInPicture() && checkPictureInPictureSupport)
+ // If the activity can be embedded, it should inherit the bounds of task fragment.
+ || isEmbedded();
}
/** @return whether this activity is non-resizeable but is forced to be resizable. */
@@ -2588,7 +2757,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (windowingMode == WINDOWING_MODE_PINNED && info.supportsPictureInPicture()) {
return false;
}
- if (WindowConfiguration.inMultiWindowMode(windowingMode) && supportsMultiWindow()
+ // Activity should be resizable if the task is.
+ final boolean supportsMultiWindow = task != null
+ ? task.supportsMultiWindow() || supportsMultiWindow()
+ : supportsMultiWindow();
+ if (WindowConfiguration.inMultiWindowMode(windowingMode) && supportsMultiWindow
&& !mAtmService.mForceResizableActivities) {
// The non resizable app will be letterboxed instead of being forced resizable.
return false;
@@ -2665,7 +2838,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
final ActivityInfo.WindowLayout windowLayout = info.windowLayout;
return windowLayout == null
|| tda.supportsActivityMinWidthHeightMultiWindow(windowLayout.minWidth,
- windowLayout.minHeight);
+ windowLayout.minHeight, info);
}
/**
@@ -2760,7 +2933,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
/**
* @return Whether AppOps allows this package to enter picture-in-picture.
*/
- private boolean checkEnterPictureInPictureAppOpsState() {
+ boolean checkEnterPictureInPictureAppOpsState() {
return mAtmService.getAppOpsManager().checkOpNoThrow(
OP_PICTURE_IN_PICTURE, info.applicationInfo.uid, packageName) == MODE_ALLOWED;
}
@@ -2900,7 +3073,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
@interface FinishRequest {}
/**
- * See {@link #finishIfPossible(int, Intent, String, boolean)}
+ * See {@link #finishIfPossible(int, Intent, NeededUriGrants, String, boolean)}
*/
@FinishRequest int finishIfPossible(String reason, boolean oomAdj) {
return finishIfPossible(Activity.RESULT_CANCELED,
@@ -2934,7 +3107,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
final Task rootTask = getRootTask();
- final boolean mayAdjustTop = (isState(RESUMED) || rootTask.getResumedActivity() == null)
+ final boolean mayAdjustTop = (isState(RESUMED) || rootTask.getTopResumedActivity() == null)
&& rootTask.isFocusedRootTaskOnDisplay()
// Do not adjust focus task because the task will be reused to launch new activity.
&& !task.isClearingToReuseTask();
@@ -2945,9 +3118,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
mAtmService.deferWindowLayout();
try {
- final Transition newTransition = (!mAtmService.getTransitionController().isCollecting()
- && mAtmService.getTransitionController().getTransitionPlayer() != null)
- ? mAtmService.getTransitionController().createTransition(TRANSIT_CLOSE) : null;
mTaskSupervisor.mNoHistoryActivities.remove(this);
makeFinishingLocked();
// Make a local reference to its task since this.task could be set to null once this
@@ -2979,10 +3149,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
- if (newTransition != null) {
- mAtmService.getTransitionController().requestStartTransition(newTransition,
- endTask ? task : null, null /* remote */);
- }
+ mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
if (isState(RESUMED)) {
if (endTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -3013,12 +3180,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Tell window manager to prepare for this one to be removed.
setVisibility(false);
- if (task.getPausingActivity() == null) {
+ if (getTaskFragment().getPausingActivity() == null) {
ProtoLog.v(WM_DEBUG_STATES, "Finish needs to pause: %s", this);
if (DEBUG_USER_LEAVING) {
Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false");
}
- task.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+ getTaskFragment().startPausing(false /* userLeaving */, false /* uiSleeping */,
null /* resuming */, "finish");
}
@@ -3140,6 +3307,20 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// TODO(b/137329632): find the next activity directly underneath this one, not just anywhere
final ActivityRecord next = getDisplayArea().topRunningActivity(
true /* considerKeyguardState */);
+
+ // If the finishing activity is the last activity of a organized TaskFragment and has an
+ // adjacent TaskFragment, check if the activity removal should be delayed.
+ boolean delayRemoval = false;
+ final TaskFragment taskFragment = getTaskFragment();
+ if (next != null && taskFragment != null && taskFragment.isEmbedded()) {
+ final TaskFragment organized = taskFragment.getOrganizedTaskFragment();
+ final TaskFragment adjacent =
+ organized != null ? organized.getAdjacentTaskFragment() : null;
+ if (adjacent != null && organized.topRunningActivity() == null) {
+ delayRemoval = organized.isDelayLastActivityRemoval();
+ }
+ }
+
// isNextNotYetVisible is to check if the next activity is invisible, or it has been
// requested to be invisible but its windows haven't reported as invisible. If so, it
// implied that the current finishing activity should be added into stopping list rather
@@ -3149,12 +3330,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Clear last paused activity to ensure top activity can be resumed during sleeping.
if (isNextNotYetVisible && mDisplayContent.isSleeping()
- && next == next.getRootTask().mLastPausedActivity) {
- next.getRootTask().mLastPausedActivity = null;
+ && next == next.getTaskFragment().mLastPausedActivity) {
+ next.getTaskFragment().clearLastPausedActivity();
}
if (isCurrentVisible) {
- if (isNextNotYetVisible) {
+ if (isNextNotYetVisible || delayRemoval) {
// Add this activity to the list of stopping activities. It will be processed and
// destroyed when the next activity reports idle.
addToStopping(false /* scheduleIdle */, false /* idleDelayed */,
@@ -3344,8 +3525,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (DEBUG_SWITCH) {
final Task task = getTask();
Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState()
- + " resumed=" + task.getResumedActivity()
- + " pausing=" + task.getPausingActivity()
+ + " resumed=" + task.getTopResumedActivity()
+ + " pausing=" + task.getTopPausingActivity()
+ " for reason " + reason);
}
return destroyImmediately(reason);
@@ -3370,7 +3551,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
setState(DESTROYED, "removeFromHistory");
if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this);
detachFromProcess();
- removeAppTokenFromDisplay();
+ // Resume key dispatching if it is currently paused before we remove the container.
+ resumeKeyDispatchingLocked();
+ mDisplayContent.removeAppToken(appToken);
cleanUpActivityServices();
removeUriPermissionsLocked();
@@ -3388,10 +3571,18 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return;
}
finishing = true;
+ final TaskFragment taskFragment = getTaskFragment();
+ if (taskFragment != null) {
+ final Task task = taskFragment.getTask();
+ if (task != null && task.isClearingToReuseTask()
+ && taskFragment.getTopNonFinishingActivity() == null) {
+ taskFragment.mClearedTaskForReuse = true;
+ }
+ taskFragment.sendTaskFragmentInfoChanged();
+ }
if (stopped) {
abortAndClearOptionsAnimation();
}
- mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CLOSE, this);
}
/**
@@ -3425,7 +3616,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* Note: Call before {@link #removeFromHistory(String)}.
*/
void cleanUp(boolean cleanServices, boolean setState) {
- task.cleanUpActivityReferences(this);
+ getTaskFragment().cleanUpActivityReferences(this);
clearLastParentBeforePip();
// Clean up the splash screen if it was still displayed.
@@ -3486,6 +3677,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (mPendingRelaunchCount > 0) {
mPendingRelaunchCount--;
+ if (mPendingRelaunchCount == 0 && !isClientVisible()) {
+ // Don't count if the client won't report drawn.
+ mRelaunchStartTime = 0;
+ }
} else {
// Update keyguard flags upon finishing relaunch.
checkKeyguardFlagsChanged();
@@ -3534,7 +3729,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// failed more than twice. Skip activities that's already finishing cleanly by itself.
remove = false;
} else if ((!mHaveState && !stateNotNeeded
- && !isState(ActivityState.RESTARTING_PROCESS)) || finishing) {
+ && !isState(State.RESTARTING_PROCESS)) || finishing) {
// Don't currently have state for the activity, or it is finishing -- always remove it.
remove = true;
} else if (!mVisibleRequested && launchCount > 2
@@ -3570,20 +3765,36 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// to the restarted activity.
nowVisible = mVisibleRequested;
}
+ mTransitionController.requestCloseTransitionIfNeeded(this);
cleanUp(true /* cleanServices */, true /* setState */);
if (remove) {
+ if (mStartingData != null && mVisible && task != null) {
+ // A corner case that the app terminates its trampoline activity on a separated
+ // process by killing itself. Transfer the starting window to the next activity
+ // which will be visible, so the dead activity can be removed immediately (no
+ // longer animating) and the reveal animation can play normally on next activity.
+ final ActivityRecord top = task.topRunningActivity();
+ if (top != null && !top.mVisible && top.shouldBeVisible()) {
+ top.transferStartingWindow(this);
+ }
+ }
removeFromHistory("appDied");
}
}
@Override
void removeImmediately() {
- if (!finishing) {
+ if (mState != DESTROYED) {
+ Slog.w(TAG, "Force remove immediately " + this + " state=" + mState);
// If Task#removeImmediately is called directly with alive activities, ensure that the
// activities are destroyed and detached from process.
destroyImmediately("removeImmediately");
+ // Complete the destruction immediately because this activity will not be found in
+ // hierarchy, it is unable to report completion.
+ destroyed("removeImmediately");
+ } else {
+ onRemovedFromDisplay();
}
- onRemovedFromDisplay();
super.removeImmediately();
}
@@ -3610,8 +3821,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Removing app token: %s", this);
- commitVisibility(false /* visible */, true /* performLayout */);
-
getDisplayContent().mOpeningApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mTaskSnapshotController.onAppRemoved(this);
@@ -3619,8 +3828,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
mTaskSupervisor.mStoppingActivities.remove(this);
waitingToShow = false;
- // TODO(b/169035022): move to a more-appropriate place.
- mAtmService.getTransitionController().collect(this);
// Defer removal of this activity when either a child is animating, or app transition is on
// going. App transition animation might be applied on the parent task not on the activity,
// but the actual frame buffer is associated with the activity, so we have to keep the
@@ -3632,10 +3839,20 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
} else if (getDisplayContent().mAppTransition.isTransitionSet()) {
getDisplayContent().mClosingApps.add(this);
delayed = true;
- } else if (mAtmService.getTransitionController().inTransition()) {
+ } else if (mTransitionController.inTransition()) {
delayed = true;
}
+ // Don't commit visibility if it is waiting to animate. It will be set post animation.
+ if (!delayed) {
+ commitVisibility(false /* visible */, true /* performLayout */);
+ } else {
+ setVisibleRequested(false /* visible */);
+ }
+
+ // TODO(b/169035022): move to a more-appropriate place.
+ mTransitionController.collect(this);
+
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Removing app %s delayed=%b animation=%s animating=%b", this, delayed,
getAnimation(),
@@ -3797,17 +4014,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
- boolean transferStartingWindow(IBinder transferFrom) {
- final ActivityRecord fromActivity = getDisplayContent().getActivityRecord(transferFrom);
- if (fromActivity == null) {
- return false;
- }
-
+ private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) {
final WindowState tStartingWindow = fromActivity.mStartingWindow;
if (tStartingWindow != null && fromActivity.mStartingSurface != null) {
// In this case, the starting icon has already been displayed, so start
// letting windows get shown immediately without any more transitions.
- getDisplayContent().mSkipAppTransitionAnimation = true;
+ if (fromActivity.mVisible) {
+ mDisplayContent.mSkipAppTransitionAnimation = true;
+ }
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving existing starting %s"
+ " from %s to %s", tStartingWindow, fromActivity, this);
@@ -3823,6 +4037,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Transfer the starting window over to the new token.
mStartingData = fromActivity.mStartingData;
+ mSharedStartingData = fromActivity.mSharedStartingData;
mStartingSurface = fromActivity.mStartingSurface;
startingDisplayed = fromActivity.startingDisplayed;
fromActivity.startingDisplayed = false;
@@ -3837,7 +4052,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
ProtoLog.v(WM_DEBUG_ADD_REMOVE,
"Removing starting %s from %s", tStartingWindow, fromActivity);
- mAtmService.getTransitionController().collect(tStartingWindow);
+ mTransitionController.collect(tStartingWindow);
tStartingWindow.reparent(this, POSITION_TOP);
// Propagate other interesting state between the tokens. If the old token is displayed,
@@ -3863,7 +4078,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// the token we transfer the animation over. Thus, set this flag to indicate
// we've transferred the animation.
mUseTransferredAnimation = true;
- } else if (mAtmService.getTransitionController().getTransitionPlayer() != null) {
+ } else if (mTransitionController.getTransitionPlayer() != null) {
// In the new transit system, just set this every time we transfer the window
mUseTransferredAnimation = true;
}
@@ -3885,6 +4100,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Moving pending starting from %s to %s", fromActivity, this);
mStartingData = fromActivity.mStartingData;
+ mSharedStartingData = fromActivity.mSharedStartingData;
fromActivity.mStartingData = null;
fromActivity.startingMoved = true;
scheduleAddStartingWindow();
@@ -3903,16 +4119,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* immediately finishes after, so we have to transfer T to M.
*/
void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
- final PooledFunction p = PooledLambda.obtainFunction(ActivityRecord::transferStartingWindow,
- this, PooledLambda.__(ActivityRecord.class));
- task.forAllActivities(p);
- p.recycle();
- }
-
- private boolean transferStartingWindow(ActivityRecord fromActivity) {
- if (fromActivity == this) return true;
-
- return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity.token);
+ task.forAllActivities(fromActivity -> {
+ if (fromActivity == this) return true;
+ return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity);
+ });
}
void checkKeyguardFlagsChanged() {
@@ -3981,19 +4191,40 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* conditions a) above.
* Multi-windowing mode will be exited if {@code true} is returned.
*/
- boolean canShowWhenLocked() {
- if (!inPinnedWindowingMode() && (mShowWhenLocked || containsShowWhenLockedWindow())) {
+ private static boolean canShowWhenLocked(ActivityRecord r) {
+ if (r == null || r.getTaskFragment() == null) {
+ return false;
+ }
+ if (!r.inPinnedWindowingMode() && (r.mShowWhenLocked || r.containsShowWhenLockedWindow())) {
return true;
- } else if (mInheritShownWhenLocked) {
- final ActivityRecord r = task.getActivityBelow(this);
- return r != null && !r.inPinnedWindowingMode() && (r.mShowWhenLocked
- || r.containsShowWhenLockedWindow());
+ } else if (r.mInheritShownWhenLocked) {
+ final ActivityRecord activity = r.getTaskFragment().getActivityBelow(r);
+ return activity != null && !activity.inPinnedWindowingMode()
+ && (activity.mShowWhenLocked || activity.containsShowWhenLockedWindow());
} else {
return false;
}
}
/**
+ * Determines if the activity can show while lock-screen is displayed. System displays
+ * activities while lock-screen is displayed only if all activities
+ * {@link #canShowWhenLocked(ActivityRecord)}.
+ * @see #canShowWhenLocked(ActivityRecord)
+ */
+ boolean canShowWhenLocked() {
+ final TaskFragment taskFragment = getTaskFragment();
+ if (taskFragment != null && taskFragment.getAdjacentTaskFragment() != null
+ && taskFragment.isEmbedded()) {
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ final ActivityRecord r = adjacentTaskFragment.getTopNonFinishingActivity();
+ return canShowWhenLocked(this) && canShowWhenLocked(r);
+ } else {
+ return canShowWhenLocked(this);
+ }
+ }
+
+ /**
* @return Whether we are allowed to show non-starting windows at the moment. We disallow
* showing windows during transitions in case we have windows that have wide-color-gamut
* color mode set to avoid jank in the middle of the transition.
@@ -4068,20 +4299,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return callback.test(this) ? this : null;
}
- @Override
- protected void setLayer(Transaction t, int layer) {
- if (!mSurfaceAnimator.hasLeash()) {
- t.setLayer(mSurfaceControl, layer);
- }
- }
-
- @Override
- protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
- if (!mSurfaceAnimator.hasLeash()) {
- t.setRelativeLayer(mSurfaceControl, relativeTo, layer);
- }
- }
-
void logStartActivity(int tag, Task task) {
final Uri data = intent.getData();
final String strData = data != null ? data.toSafeString() : null;
@@ -4258,6 +4475,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
private void applyOptionsAnimation(ActivityOptions pendingOptions, Intent intent) {
final int animationType = pendingOptions.getAnimationType();
final DisplayContent displayContent = getDisplayContent();
+ AnimationOptions options = null;
+ IRemoteCallback startCallback = null;
+ IRemoteCallback finishCallback = null;
switch (animationType) {
case ANIM_CUSTOM:
displayContent.mAppTransition.overridePendingAppTransition(
@@ -4267,11 +4487,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pendingOptions.getAnimationStartedListener(),
pendingOptions.getAnimationFinishedListener(),
pendingOptions.getOverrideTaskTransition());
+ options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(),
+ pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(),
+ pendingOptions.getOverrideTaskTransition());
+ startCallback = pendingOptions.getAnimationStartedListener();
+ finishCallback = pendingOptions.getAnimationFinishedListener();
break;
case ANIM_CLIP_REVEAL:
displayContent.mAppTransition.overridePendingAppTransitionClipReveal(
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getWidth(), pendingOptions.getHeight());
+ options = AnimationOptions.makeClipRevealAnimOptions(
+ pendingOptions.getStartX(), pendingOptions.getStartY(),
+ pendingOptions.getWidth(), pendingOptions.getHeight());
if (intent.getSourceBounds() == null) {
intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
pendingOptions.getStartY(),
@@ -4283,6 +4511,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
displayContent.mAppTransition.overridePendingAppTransitionScaleUp(
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getWidth(), pendingOptions.getHeight());
+ options = AnimationOptions.makeScaleUpAnimOptions(
+ pendingOptions.getStartX(), pendingOptions.getStartY(),
+ pendingOptions.getWidth(), pendingOptions.getHeight());
if (intent.getSourceBounds() == null) {
intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
pendingOptions.getStartY(),
@@ -4298,6 +4529,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
pendingOptions.getStartX(), pendingOptions.getStartY(),
pendingOptions.getAnimationStartedListener(),
scaleUp);
+ options = AnimationOptions.makeThumnbnailAnimOptions(buffer,
+ pendingOptions.getStartX(), pendingOptions.getStartY(), scaleUp);
+ startCallback = pendingOptions.getAnimationStartedListener();
if (intent.getSourceBounds() == null && buffer != null) {
intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
pendingOptions.getStartY(),
@@ -4337,6 +4571,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
case ANIM_OPEN_CROSS_PROFILE_APPS:
displayContent.mAppTransition
.overridePendingAppTransitionStartCrossProfileApps();
+ options = AnimationOptions.makeCrossProfileAnimOptions();
break;
case ANIM_NONE:
case ANIM_UNDEFINED:
@@ -4345,6 +4580,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
Slog.e(TAG_WM, "applyOptionsLocked: Unknown animationType=" + animationType);
break;
}
+
+ if (options != null) {
+ mTransitionController.setOverrideAnimation(options, startCallback, finishCallback);
+ }
}
void clearAllDrawn() {
@@ -4424,8 +4663,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return opts;
}
- IRemoteTransition takeRemoteTransition() {
- IRemoteTransition out = mPendingRemoteTransition;
+ RemoteTransition takeRemoteTransition() {
+ RemoteTransition out = mPendingRemoteTransition;
mPendingRemoteTransition = null;
return out;
}
@@ -4477,6 +4716,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
+ boolean getDeferHidingClient() {
+ return mDeferHidingClient;
+ }
+
@Override
boolean isVisible() {
// If the activity isn't hidden then it is considered visible and there is no need to check
@@ -4512,6 +4755,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (app != null) {
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
+ logAppCompatState();
}
/**
@@ -4569,7 +4813,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
Debug.getCallers(6));
// Before setting mVisibleRequested so we can track changes.
- mAtmService.getTransitionController().collect(this);
+ mTransitionController.collect(this);
onChildVisibilityRequested(visible);
@@ -4641,7 +4885,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
// If in a transition, defer commits for activities that are going invisible
- if (!visible && mAtmService.getTransitionController().inTransition()) {
+ if (!visible && inTransition()) {
return;
}
// If we are preparing an app transition, then delay changing
@@ -4705,8 +4949,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* @param visible {@code true} if this {@link ActivityRecord} should become visible, otherwise
* this should become invisible.
* @param performLayout if {@code true}, perform surface placement after committing visibility.
+ * @param fromTransition {@code true} if this is part of finishing a transition.
*/
- void commitVisibility(boolean visible, boolean performLayout) {
+ void commitVisibility(boolean visible, boolean performLayout, boolean fromTransition) {
// Reset the state of mVisibleSetFromTransferredStartingWindow since visibility is actually
// been set by the app now.
mVisibleSetFromTransferredStartingWindow = false;
@@ -4726,7 +4971,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
} else {
// If we are being set visible, and the starting window is not yet displayed,
// then make sure it doesn't get displayed.
- if (mStartingWindow != null && !mStartingWindow.isDrawn()) {
+ if (mStartingWindow != null && !mStartingWindow.isDrawn()
+ && (firstWindowDrawn || allDrawn)) {
mStartingWindow.clearPolicyVisibilityFlag(LEGACY_POLICY_VISIBILITY);
mStartingWindow.mLegacyPolicyVisibilityAfterAnim = false;
}
@@ -4734,6 +4980,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// getting visible so we also wait for them.
forAllWindows(mWmService::makeWindowFreezingScreenIfNeededLocked, true);
}
+ // dispatchTaskInfoChangedIfNeeded() right after ActivityRecord#setVisibility() can report
+ // the stale visible state, because the state will be updated after the app transition.
+ // So tries to report the actual visible state again where the state is changed.
+ Task task = getOrganizedTask();
+ while (task != null) {
+ task.dispatchTaskInfoChangedIfNeeded(false /* force */);
+ task = task.getParent().asTask();
+ }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
isVisible(), mVisibleRequested);
@@ -4747,7 +5001,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
mUseTransferredAnimation = false;
- postApplyAnimation(visible);
+ postApplyAnimation(visible, fromTransition);
+ }
+
+ void commitVisibility(boolean visible, boolean performLayout) {
+ commitVisibility(visible, performLayout, false /* fromTransition */);
}
/**
@@ -4758,12 +5016,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
*
* @param visible {@code true} if this {@link ActivityRecord} has become visible, otherwise
* this has become invisible.
+ * @param fromTransition {@code true} if this call is part of finishing a transition. This is
+ * needed because the shell transition is no-longer active by the time
+ * commitVisibility is called.
*/
- private void postApplyAnimation(boolean visible) {
+ private void postApplyAnimation(boolean visible, boolean fromTransition) {
+ final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
final boolean delayed = isAnimating(PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
- if (!delayed) {
+ if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
// which we might have done because we were in closing/opening apps list.
@@ -4782,8 +5044,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// updated.
// If we're becoming invisible, update the client visibility if we are not running an
// animation. Otherwise, we'll update client visibility in onAnimationFinished.
- if (visible || !isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
+ if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+ || usingShellTransitions) {
setClientVisible(visible);
}
@@ -4799,7 +5061,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
final DisplayContent displayContent = getDisplayContent();
if (!displayContent.mClosingApps.contains(this)
- && !displayContent.mOpeningApps.contains(this)) {
+ && !displayContent.mOpeningApps.contains(this)
+ && !fromTransition) {
// Take the screenshot before possibly hiding the WSA, otherwise the screenshot
// will not be taken.
mWmService.mTaskSnapshotController.notifyAppVisibilityChanged(this, visible);
@@ -4893,7 +5156,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return mCurrentLaunchCanTurnScreenOn;
}
- void setState(ActivityState state, String reason) {
+ void setState(State state, String reason) {
ProtoLog.v(WM_DEBUG_STATES, "State movement: %s from:%s to:%s reason:%s",
this, getState(), state, reason);
@@ -4907,8 +5170,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
callServiceTrackeronActivityStatechange(state, false);
- if (task != null) {
- task.onActivityStateChanged(this, state, reason);
+ if (getTaskFragment() != null) {
+ getTaskFragment().onActivityStateChanged(this, state, reason);
}
// The WindowManager interprets the app stopping signal as
@@ -4968,7 +5231,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
- void callServiceTrackeronActivityStatechange(ActivityState state, boolean early_notify) {
+ void callServiceTrackeronActivityStatechange(State state, boolean early_notify) {
IServicetracker mServicetracker;
ActivityDetails aDetails = new ActivityDetails();
ActivityStats aStats = new ActivityStats();
@@ -5038,44 +5301,42 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
- ActivityState getState() {
+ State getState() {
return mState;
}
/**
* Returns {@code true} if the Activity is in the specified state.
*/
- boolean isState(ActivityState state) {
+ boolean isState(State state) {
return state == mState;
}
/**
* Returns {@code true} if the Activity is in one of the specified states.
*/
- boolean isState(ActivityState state1, ActivityState state2) {
+ boolean isState(State state1, State state2) {
return state1 == mState || state2 == mState;
}
/**
* Returns {@code true} if the Activity is in one of the specified states.
*/
- boolean isState(ActivityState state1, ActivityState state2, ActivityState state3) {
+ boolean isState(State state1, State state2, State state3) {
return state1 == mState || state2 == mState || state3 == mState;
}
/**
* Returns {@code true} if the Activity is in one of the specified states.
*/
- boolean isState(ActivityState state1, ActivityState state2, ActivityState state3,
- ActivityState state4) {
+ boolean isState(State state1, State state2, State state3, State state4) {
return state1 == mState || state2 == mState || state3 == mState || state4 == mState;
}
/**
* Returns {@code true} if the Activity is in one of the specified states.
*/
- boolean isState(ActivityState state1, ActivityState state2, ActivityState state3,
- ActivityState state4, ActivityState state5) {
+ boolean isState(State state1, State state2, State state3, State state4, State state5) {
return state1 == mState || state2 == mState || state3 == mState || state4 == mState
|| state5 == mState;
}
@@ -5083,8 +5344,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
/**
* Returns {@code true} if the Activity is in one of the specified states.
*/
- boolean isState(ActivityState state1, ActivityState state2, ActivityState state3,
- ActivityState state4, ActivityState state5, ActivityState state6) {
+ boolean isState(State state1, State state2, State state3, State state4, State state5,
+ State state6) {
return state1 == mState || state2 == mState || state3 == mState || state4 == mState
|| state5 == mState || state6 == mState;
}
@@ -5141,6 +5402,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
void notifyAppStopped() {
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this);
mAppStopped = true;
+ firstWindowDrawn = false;
// This is to fix the edge case that auto-enter-pip is finished in Launcher but app calls
// setAutoEnterEnabled(false) and transitions to STOPPED state, see b/191930787.
// Clear any surface transactions and content overlay in this case.
@@ -5212,7 +5474,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
void updateVisibilityIgnoringKeyguard(boolean behindFullscreenActivity) {
visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind)
- && okToShowLocked();
+ && showToCurrentUser();
}
boolean shouldBeVisible() {
@@ -5282,7 +5544,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// returns. Just need to confirm this reasoning makes sense.
final boolean deferHidingClient = canEnterPictureInPicture
&& !isState(STARTED, STOPPING, STOPPED, PAUSED);
- if (deferHidingClient && pictureInPictureArgs.isAutoEnterEnabled()) {
+ if (!mTransitionController.isShellTransitionsEnabled()
+ && deferHidingClient && pictureInPictureArgs.isAutoEnterEnabled()) {
// Go ahead and just put the activity in pip if it supports auto-pip.
mAtmService.enterPictureInPictureMode(this, pictureInPictureArgs);
return;
@@ -5298,13 +5561,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
supportsEnterPipOnTaskSwitch = false;
break;
case RESUMED:
- // If the app is capable of entering PIP, we should try pausing it now
- // so it can PIP correctly.
- if (deferHidingClient) {
- task.startPausingLocked(false /* uiSleeping */,
- null /* resuming */, "makeInvisible");
- break;
- }
case INITIALIZING:
case PAUSING:
case PAUSED:
@@ -5403,7 +5659,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
*/
private boolean shouldBeResumed(ActivityRecord activeActivity) {
return shouldMakeActive(activeActivity) && isFocusable()
- && getTask().getVisibility(activeActivity) == TASK_VISIBILITY_VISIBLE
+ && getTaskFragment().getVisibility(activeActivity)
+ == TASK_FRAGMENT_VISIBILITY_VISIBLE
&& canResumeByCompat();
}
@@ -5457,7 +5714,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (!task.hasChild(this)) {
throw new IllegalStateException("Activity not found in its task");
}
- return task.topRunningActivity() == this;
+ return getTaskFragment().topRunningActivity() == this;
}
void handleAlreadyVisible() {
@@ -5551,16 +5808,17 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
ProtoLog.v(WM_DEBUG_STATES, "Activity paused: token=%s, timeout=%b", appToken,
timeout);
- if (task != null) {
+ final TaskFragment taskFragment = getTaskFragment();
+ if (taskFragment != null) {
removePauseTimeout();
- final ActivityRecord pausingActivity = task.getPausingActivity();
+ final ActivityRecord pausingActivity = taskFragment.getPausingActivity();
if (pausingActivity == this) {
ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSED: %s %s", this,
(timeout ? "(due to timeout)" : " (pause complete)"));
mAtmService.deferWindowLayout();
try {
- task.completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
+ taskFragment.completePause(true /* resumeNext */, null /* resumingActivity */);
} finally {
mAtmService.continueWindowLayout();
}
@@ -5821,6 +6079,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
void startFreezingScreen(int overrideOriginalDisplayRotation) {
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ return;
+ }
ProtoLog.i(WM_DEBUG_ORIENTATION,
"Set freezing of %s: visible=%b freezing=%b visibleRequested=%b. %s",
appToken, isVisible(), mFreezingScreen, mVisibleRequested,
@@ -5921,7 +6182,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
- void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
+ void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
// stop tracking
mSplashScreenStyleEmpty = true;
@@ -5937,7 +6198,22 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// own stuff.
win.cancelAnimation();
}
- removeStartingWindow();
+
+ // Remove starting window directly if is in a pure task. Otherwise if it is associated with
+ // a task (e.g. nested task fragment), then remove only if all visible windows in the task
+ // are drawn.
+ final Task associatedTask =
+ mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+ if (associatedTask == null) {
+ removeStartingWindow();
+ } else if (associatedTask.getActivity(
+ r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
+ // The last drawn activity may not be the one that owns the starting window.
+ final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
+ if (r != null) {
+ r.removeStartingWindow();
+ }
+ }
updateReportedVisibilityLocked();
}
@@ -5981,6 +6257,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (task != null) {
task.setHasBeenVisible(true);
}
+ // Clear indicated launch root task because there's no trampoline activity to expect after
+ // the windows are drawn.
+ mLaunchRootTask = null;
}
/** Called when the windows associated app window container are visible. */
@@ -6116,11 +6395,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions.
// pip activities should just remain in clientVisible.
if (!clientVisible && mDeferHidingClient) return;
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "setClientVisible: %s clientVisible=%b Callers=%s", this, clientVisible,
- Debug.getCallers(5));
super.setClientVisible(clientVisible);
- sendAppVisibilityToClients();
}
/**
@@ -6239,9 +6514,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return this;
}
// Try to use the one which is closest to top.
- ActivityRecord r = rootTask.getResumedActivity();
+ ActivityRecord r = rootTask.getTopResumedActivity();
if (r == null) {
- r = rootTask.getPausingActivity();
+ r = rootTask.getTopPausingActivity();
}
if (r != null) {
return r;
@@ -6250,22 +6525,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return this;
}
- /** Checks whether the activity should be shown for current user. */
- public boolean okToShowLocked() {
- // We cannot show activities when the device is locked and the application is not
- // encryption aware.
- if (!StorageManager.isUserKeyUnlocked(mUserId)
- && !info.applicationInfo.isEncryptionAware()) {
- return false;
- }
-
- return (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0
- || (mTaskSupervisor.isCurrentProfileLocked(mUserId)
- && mAtmService.mAmInternal.isUserRunning(mUserId, 0 /* flags */));
- }
-
boolean canBeTopRunning() {
- return !finishing && okToShowLocked();
+ return !finishing && showToCurrentUser();
}
/**
@@ -6302,6 +6563,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return null;
}
+ @Nullable
+ static ActivityRecord isInAnyTask(IBinder token) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ return (r != null && r.isAttached()) ? r : null;
+ }
+
/**
* @return display id to which this record is attached,
* {@link android.view.Display#INVALID_DISPLAY} if not attached.
@@ -6319,7 +6586,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// This would be redundant.
return false;
}
- if (isState(RESUMED) || getRootTask() == null || this == task.getPausingActivity()
+ if (isState(RESUMED) || getRootTask() == null
+ || this == getTaskFragment().getPausingActivity()
|| !mHaveState || !stopped) {
// We're not ready for this kind of thing.
return false;
@@ -6426,6 +6694,13 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
} else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON) {
return false;
}
+ // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is
+ // not specified in the ActivityOptions.
+ if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME) {
+ return false;
+ } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
+ return true;
+ }
}
if (sourceRecord == null) {
sourceRecord = searchCandidateLaunchingActivity();
@@ -6435,14 +6710,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return sourceRecord.mSplashScreenStyleEmpty;
}
- // If this activity was launched from a system surface, never use an empty splash screen
+ // If this activity was launched from Launcher or System for first start, never use an
+ // empty splash screen.
// Need to check sourceRecord before in case this activity is launched from service.
- if (launchedFromSystemSurface()) {
- return false;
- }
-
- // Otherwise use empty.
- return true;
+ return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM
+ || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME);
}
private int getSplashscreenTheme() {
@@ -6506,13 +6778,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
final boolean newSingleActivity = !newTask && !activityCreated
&& task.getActivity((r) -> !r.finishing && r != this) == null;
- final boolean shown = addStartingWindow(packageName, resolvedTheme,
+ final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
- prev != null ? prev.appToken : null,
- newTask || newSingleActivity, taskSwitch, isProcessRunning(),
+ prev, newTask || newSingleActivity, taskSwitch, isProcessRunning(),
allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty);
- if (shown) {
- mStartingWindowState = STARTING_WINDOW_SHOWN;
+ if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
+ Slog.d(TAG, "Scheduled starting window for " + this);
}
}
@@ -6524,14 +6795,12 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* It should only be called if this activity is behind other fullscreen activity.
*/
void cancelInitializing() {
- if (mStartingWindowState == STARTING_WINDOW_SHOWN) {
+ if (mStartingData != null) {
// Remove orphaned starting window.
if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
- mStartingWindowState = STARTING_WINDOW_REMOVED;
removeStartingWindowAnimation(false /* prepareAnimation */);
}
- if (isState(INITIALIZING) && !shouldBeVisible(
- true /* behindFullscreenActivity */, true /* ignoringKeyguard */)) {
+ if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
// Remove the unknown visibility record because an invisible activity shouldn't block
// the keyguard transition.
mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
@@ -6576,17 +6845,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
}
- boolean hasWindowsAlive() {
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- // No need to loop through child windows as the answer should be the same as that of the
- // parent window.
- if (!(mChildren.get(i)).mAppDied) {
- return true;
- }
- }
- return false;
- }
-
void setWillReplaceWindows(boolean animate) {
ProtoLog.d(WM_DEBUG_ADD_REMOVE,
"Marking app token %s with replacing windows.", this);
@@ -6670,12 +6928,6 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return candidate;
}
- SurfaceControl getAppAnimationLayer() {
- return getAppAnimationLayer(isActivityTypeHome() ? ANIMATION_LAYER_HOME
- : needsZBoost() ? ANIMATION_LAYER_BOOSTED
- : ANIMATION_LAYER_STANDARD);
- }
-
@Override
boolean needsZBoost() {
return mNeedsZBoost || super.needsZBoost();
@@ -6745,29 +6997,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
|| mDisplayContent.isNextTransitionForward();
}
- private int getAnimationLayer() {
- // The leash is parented to the animation layer. We need to preserve the z-order by using
- // the prefix order index, but we boost if necessary.
- int layer;
- if (!inPinnedWindowingMode()) {
- layer = getPrefixOrderIndex();
- } else {
- // Root pinned tasks have animations take place within themselves rather than an
- // animation layer so we need to preserve the order relative to the root task (e.g.
- // the order of our task/parent).
- layer = getParent().getPrefixOrderIndex();
- }
-
- if (mNeedsZBoost) {
- layer += Z_BOOST_BASE;
- }
- return layer;
- }
-
@Override
- public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
- t.setLayer(leash, getAnimationLayer());
- getDisplayContent().assignRootTaskOrdering();
+ void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) {
+ // Noop as Activity may be offset for letterbox
}
@Override
@@ -6796,7 +7028,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Crop to root task bounds.
t.setLayer(leash, 0);
- t.setLayer(mAnimationBoundsLayer, getAnimationLayer());
+ t.setLayer(mAnimationBoundsLayer, getLastLayer());
// Reparent leash to animation bounds layer.
t.reparent(leash, mAnimationBoundsLayer);
@@ -6889,7 +7121,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
Rect appRect;
if (win != null) {
insets = win.getInsetsStateWithVisibilityOverride().calculateInsets(
- win.getFrame(), Type.systemBars(), false /* ignoreVisibility */);
+ win.getFrame(), Type.systemBars(), false /* ignoreVisibility */).toRect();
appRect = new Rect(win.getFrame());
appRect.inset(insets);
} else {
@@ -6898,8 +7130,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
final Configuration displayConfig = mDisplayContent.getConfiguration();
return getDisplayContent().mAppTransition.createThumbnailAspectScaleAnimationLocked(
- appRect, insets, thumbnailHeader, task, displayConfig.uiMode,
- displayConfig.orientation);
+ appRect, insets, thumbnailHeader, task, displayConfig.orientation);
}
@Override
@@ -7046,7 +7277,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return;
}
- mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform(task);
+ mDisplayContent.mPinnedTaskController.onCancelFixedRotationTransform();
// Perform rotation animation according to the rotation of this activity.
startFreezingScreen(originalDisplayRotation);
// This activity may relaunch or perform configuration change so once it has reported drawn,
@@ -7202,7 +7433,11 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return false;
}
}
- return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
+ // Activity should be resizable if the task is.
+ final boolean isResizeable = task != null
+ ? task.isResizeable() || isResizeable()
+ : isResizeable();
+ return !isResizeable && (info.isFixedOrientation() || hasFixedAspectRatio())
// The configuration of non-standard type should be enforced by system.
// {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is
// added to a task, but this function is called when resolving the launch params, at
@@ -7324,7 +7559,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// If the activity has requested override bounds, the configuration needs to be
// computed accordingly.
if (!matchParentBounds()) {
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+ getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
+ newParentConfiguration);
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
@@ -7367,14 +7603,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
+ "should create compatDisplayInsets = %s",
getUid(),
mTmpBounds,
- info.neverSandboxDisplayApis(),
- info.alwaysSandboxDisplayApis(),
+ info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
+ info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
mCompatDisplayInsets != null,
shouldCreateCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
+
+ logAppCompatState();
}
/**
@@ -7384,24 +7622,61 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* LetterboxUiController#shouldShowLetterboxUi} for more context.
*/
boolean areBoundsLetterboxed() {
+ return getAppCompatState(/* ignoreVisibility= */ true)
+ != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
+ }
+
+ /**
+ * Logs the current App Compat state via {@link ActivityMetricsLogger#logAppCompatState}.
+ */
+ private void logAppCompatState() {
+ mTaskSupervisor.getActivityMetricsLogger().logAppCompatState(this);
+ }
+
+ /**
+ * Returns the current App Compat state of this activity.
+ *
+ * <p>The App Compat state indicates whether the activity is visible and letterboxed, and if so
+ * what is the reason for letterboxing. The state is used for logging the time spent in
+ * letterbox (sliced by the reason) vs non-letterbox per app.
+ */
+ int getAppCompatState() {
+ return getAppCompatState(/* ignoreVisibility= */ false);
+ }
+
+ /**
+ * Same as {@link #getAppCompatState()} except when {@code ignoreVisibility} the visibility
+ * of the activity is ignored.
+ *
+ * @param ignoreVisibility whether to ignore the visibility of the activity and not return
+ * NOT_VISIBLE if {@code mVisibleRequested} is false.
+ */
+ private int getAppCompatState(boolean ignoreVisibility) {
+ if (!ignoreVisibility && !mVisibleRequested) {
+ return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+ }
if (mInSizeCompatModeForBounds) {
- return true;
+ return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
}
// Letterbox for fixed orientation. This check returns true only when an activity is
// letterboxed for fixed orientation. Aspect ratio restrictions are also applied if
// present. But this doesn't return true when the activity is letterboxed only because
// of aspect ratio restrictions.
if (isLetterboxedForFixedOrientationAndAspectRatio()) {
- return true;
+ return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
}
// Letterbox for limited aspect ratio.
- return mIsAspectRatioApplied;
+ if (mIsAspectRatioApplied) {
+ return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
+ }
+
+ return APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
}
/**
* Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity
* requested in the config or via an ADB command. For more context see {@link
- * WindowManagerService#getLetterboxHorizontalPositionMultiplier}.
+ * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)}.
*/
private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
@@ -7421,11 +7696,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
parentAppBounds.width(), screenResolvedBounds.width());
} else {
float positionMultiplier =
- mWmService.mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
- positionMultiplier =
- (positionMultiplier < 0.0f || positionMultiplier > 1.0f)
- // Default to central position if invalid value is provided.
- ? 0.5f : positionMultiplier;
+ mLetterboxUiController.getHorizontalPositionMultiplier(newParentConfiguration);
offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
* positionMultiplier);
}
@@ -7439,7 +7710,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
// Since bounds has changed, the configuration needs to be computed accordingly.
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+ getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
+ }
+
+ void recomputeConfiguration() {
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ }
+
+ boolean isInTransition() {
+ return mTransitionController.inTransition() // Shell transitions.
+ || isAnimating(PARENTS | TRANSITION); // Legacy transitions.
}
/**
@@ -7455,8 +7735,60 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
/**
- * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
- * change and the requested orientation is different from the parent.
+ * In some cases, applying insets to bounds changes the orientation. For example, if a
+ * close-to-square display rotates to portrait to respect a portrait orientation activity, after
+ * insets such as the status and nav bars are applied, the activity may actually have a
+ * landscape orientation. This method checks whether the orientations of the activity window
+ * with and without insets match or if the orientation with insets already matches the
+ * requested orientation. If not, it may be necessary to letterbox the window.
+ * @param parentBounds are the new parent bounds passed down to the activity and should be used
+ * to compute the stable bounds.
+ * @param outStableBounds will store the stable bounds, which are the bounds with insets
+ * applied, if orientation is not respected when insets are applied.
+ * Otherwise outStableBounds will be empty. Stable bounds should be used
+ * to compute letterboxed bounds if orientation is not respected when
+ * insets are applied.
+ */
+ private boolean orientationRespectedWithInsets(Rect parentBounds, Rect outStableBounds) {
+ outStableBounds.setEmpty();
+ if (mDisplayContent == null) {
+ return true;
+ }
+ // Only need to make changes if activity sets an orientation
+ final int requestedOrientation = getRequestedConfigurationOrientation();
+ if (requestedOrientation == ORIENTATION_UNDEFINED) {
+ return true;
+ }
+ // Compute parent orientation from bounds
+ final int orientation = parentBounds.height() >= parentBounds.width()
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ // Compute orientation from stable parent bounds (= parent bounds with insets applied)
+ final Task task = getTask();
+ task.calculateInsetFrames(mTmpOutNonDecorBounds /* outNonDecorBounds */,
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */,
+ mDisplayContent.getDisplayInfo());
+ final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ // If orientation does not match the orientation with insets applied, then a
+ // display rotation will not be enough to respect orientation. However, even if they do
+ // not match but the orientation with insets applied matches the requested orientation, then
+ // there is no need to modify the bounds because when insets are applied, the activity will
+ // have the desired orientation.
+ final boolean orientationRespectedWithInsets = orientation == orientationWithInsets
+ || orientationWithInsets == requestedOrientation;
+ if (orientationRespectedWithInsets) {
+ outStableBounds.setEmpty();
+ }
+ return orientationRespectedWithInsets;
+ }
+
+ /**
+ * Computes bounds (letterbox or pillarbox) when either:
+ * 1. The parent doesn't handle the orientation change and the requested orientation is
+ * different from the parent (see {@link DisplayContent#setIgnoreOrientationRequest()}.
+ * 2. The parent handling the orientation is not enough. This occurs when the display rotation
+ * may not be enough to respect orientation requests (see {@link
+ * ActivityRecord#orientationRespectedWithInsets}).
*
* <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
* in this method.
@@ -7464,12 +7796,28 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
int windowingMode) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
- if (handlesOrientationChangeFromDescendant()) {
+ final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
+ final Rect stableBounds = new Rect();
+ // If orientation is respected when insets are applied, then stableBounds will be empty.
+ boolean orientationRespectedWithInsets =
+ orientationRespectedWithInsets(parentBounds, stableBounds);
+ if (handlesOrientationChangeFromDescendant() && orientationRespectedWithInsets) {
// No need to letterbox because of fixed orientation. Display will handle
- // fixed-orientation requests.
+ // fixed-orientation requests and a display rotation is enough to respect requested
+ // orientation with insets applied.
return;
}
- if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable()) {
+ // Not using Task#isResizeable() or ActivityRecord#isResizeable() directly because app
+ // compatibility testing showed that android:supportsPictureInPicture="true" alone is not
+ // sufficient signal for not letterboxing an app.
+ // TODO(214602463): Remove multi-window check since orientation and aspect ratio
+ // restrictions should always be applied in multi-window.
+ final boolean isResizeable = task != null
+ // Activity should be resizable if the task is.
+ ? task.isResizeable(/* checkPictureInPictureSupport */ false)
+ || isResizeable(/* checkPictureInPictureSupport */ false)
+ : isResizeable(/* checkPictureInPictureSupport */ false);
+ if (WindowConfiguration.inMultiWindowMode(windowingMode) && isResizeable) {
// Ignore orientation request for resizable apps in multi window.
return;
}
@@ -7486,7 +7834,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// If the activity requires a different orientation (either by override or activityInfo),
// make it fit the available bounds by scaling down its bounds.
final int forcedOrientation = getRequestedConfigurationOrientation();
- if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) {
+
+ if (forcedOrientation == ORIENTATION_UNDEFINED
+ || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) {
return;
}
@@ -7498,48 +7848,58 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return;
}
- final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
- final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
+ // TODO(b/182268157): Explore using only one type of parentBoundsWithInsets, either app
+ // bounds or stable bounds to unify aspect ratio logic.
+ final Rect parentBoundsWithInsets = orientationRespectedWithInsets
+ ? newParentConfig.windowConfiguration.getAppBounds() : stableBounds;
final Rect containingBounds = new Rect();
- final Rect containingAppBounds = new Rect();
- // Need to shrink the containing bounds into a square because the parent orientation does
- // not match the activity requested orientation.
+ final Rect containingBoundsWithInsets = new Rect();
+ // Need to shrink the containing bounds into a square because the parent orientation
+ // does not match the activity requested orientation.
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- // Shrink height to match width. Position height within app bounds.
- final int bottom = Math.min(parentAppBounds.top + parentBounds.width(),
- parentAppBounds.bottom);
- containingBounds.set(parentBounds.left, parentAppBounds.top, parentBounds.right,
+ // Landscape is defined as width > height. Make the container respect landscape
+ // orientation by shrinking height to one less than width. Landscape activity will be
+ // vertically centered within parent bounds with insets, so position vertical bounds
+ // within parent bounds with insets to prevent insets from unnecessarily trimming
+ // vertical bounds.
+ final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1,
+ parentBoundsWithInsets.bottom);
+ containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right,
bottom);
- containingAppBounds.set(parentAppBounds.left, parentAppBounds.top,
- parentAppBounds.right, bottom);
+ containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
+ parentBoundsWithInsets.right, bottom);
} else {
- // Shrink width to match height. Position width within app bounds.
- final int right = Math.min(parentAppBounds.left + parentBounds.height(),
- parentAppBounds.right);
- containingBounds.set(parentAppBounds.left, parentBounds.top, right,
+ // Portrait is defined as width <= height. Make the container respect portrait
+ // orientation by shrinking width to match height. Portrait activity will be
+ // horizontally centered within parent bounds with insets, so position horizontal bounds
+ // within parent bounds with insets to prevent insets from unnecessarily trimming
+ // horizontal bounds.
+ final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(),
+ parentBoundsWithInsets.right);
+ containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right,
parentBounds.bottom);
- containingAppBounds.set(parentAppBounds.left, parentAppBounds.top, right,
- parentAppBounds.bottom);
+ containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
+ right, parentBoundsWithInsets.bottom);
}
- Rect mTmpFullBounds = new Rect(resolvedBounds);
+ // Store the current bounds to be able to revert to size compat mode values below if needed.
+ final Rect prevResolvedBounds = new Rect(resolvedBounds);
resolvedBounds.set(containingBounds);
- // Override from config_fixedOrientationLetterboxAspectRatio or via ADB with
- // set-fixed-orientation-letterbox-aspect-ratio.
final float letterboxAspectRatioOverride =
- mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig);
final float desiredAspectRatio =
letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
// Apply aspect ratio to resolved bounds
- mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingAppBounds,
+ mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
containingBounds, desiredAspectRatio, true);
- // Vertically center if orientation is landscape. Bounds will later be horizontally centered
- // in {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
+ // Vertically center if orientation is landscape. Center within parent bounds with insets
+ // to ensure that insets do not trim height. Bounds will later be horizontally centered in
+ // {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- final int offsetY = parentBounds.centerY() - resolvedBounds.centerY();
+ final int offsetY = parentBoundsWithInsets.centerY() - resolvedBounds.centerY();
resolvedBounds.offset(0, offsetY);
}
@@ -7551,14 +7911,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// The app shouldn't be resized, we only do fixed orientation letterboxing if the
// compat bounds are also from the same fixed orientation letterbox. Otherwise,
// clear the fixed orientation bounds to show app in size compat mode.
- resolvedBounds.set(mTmpFullBounds);
+ resolvedBounds.set(prevResolvedBounds);
return;
}
}
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
- task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+ getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
+ newParentConfig);
mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
}
@@ -7586,7 +7947,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
// restrict, the bounds should be the requested override bounds.
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+ getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
getFixedRotationTransformDisplayInfo());
}
}
@@ -7646,10 +8007,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Use resolvedBounds to compute other override configurations such as appBounds. The bounds
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
- task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
+ getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
mCompatDisplayInsets);
// Use current screen layout as source because the size of app is independent to parent.
- resolvedConfig.screenLayout = Task.computeScreenLayoutOverride(
+ resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
resolvedConfig.screenHeightDp);
@@ -7662,7 +8023,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// Below figure is an example that puts an activity which was launched in a larger container
// into a smaller container.
// The outermost rectangle is the real display bounds.
- // "@" is the container app bounds (parent bounds or fixed orientation bouds)
+ // "@" is the container app bounds (parent bounds or fixed orientation bounds)
// "#" is the {@code resolvedBounds} that applies to application.
// "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled.
// ------------------------------
@@ -7705,12 +8066,23 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
mSizeCompatBounds = null;
}
- // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
- // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
+ // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal
+ // decor if needed. Horizontal position is adjusted in
+ // updateResolvedBoundsHorizontalPosition.
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
final boolean fillContainer = resolvedBounds.equals(containingBounds);
final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
- final int screenPosY = containerBounds.top;
+ // If the activity is not in size compat mode, calculate vertical centering
+ // from the container and resolved bounds.
+ // If the activity is in size compat mode, calculate vertical centering
+ // from the container and size compat bounds.
+ // The container bounds contain the parent bounds offset in the display, for
+ // example when an activity is in the lower split of split screen.
+ final int screenPosY = (mSizeCompatBounds == null
+ ? (containerBounds.height() - resolvedBounds.height()) / 2
+ : (containerBounds.height() - mSizeCompatBounds.height()) / 2)
+ + containerBounds.top;
+
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
mSizeCompatBounds.offset(screenPosX, screenPosY);
@@ -7754,13 +8126,14 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return false;
}
}
- if (info.getMinAspectRatio() > 0) {
+ final float minAspectRatio = getMinAspectRatio();
+ if (minAspectRatio > 0) {
// The activity should have at least the min aspect ratio, so this checks if the
// container still has available space to provide larger aspect ratio.
final float containerAspectRatio =
(0.5f + Math.max(containerAppWidth, containerAppHeight))
/ Math.min(containerAppWidth, containerAppHeight);
- if (containerAspectRatio <= info.getMinAspectRatio()) {
+ if (containerAspectRatio <= minAspectRatio) {
// The long side has reached the parent.
return false;
}
@@ -7793,12 +8166,16 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
if (getUid() == SYSTEM_UID) {
return false;
}
+ // Do not sandbox to activity window bounds if the feature is disabled.
+ if (mDisplayContent != null && !mDisplayContent.sandboxDisplayApis()) {
+ return false;
+ }
// Never apply sandboxing to an app that should be explicitly excluded from the config.
- if (info != null && info.neverSandboxDisplayApis()) {
+ if (info.neverSandboxDisplayApis(sConstrainDisplayApisConfig)) {
return false;
}
// Always apply sandboxing to an app that should be explicitly included from the config.
- if (info != null && info.alwaysSandboxDisplayApis()) {
+ if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) {
return true;
}
// Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
@@ -7817,13 +8194,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
@VisibleForTesting
@Override
Rect getAnimationBounds(int appRootTaskClipMode) {
- if (appRootTaskClipMode == ROOT_TASK_CLIP_BEFORE_ANIM && getRootTask() != null) {
- // Using the root task bounds here effectively applies the clipping before animation.
- return getRootTask().getBounds();
- }
- // Use task-bounds if available so that activity-level letterbox (maxAspectRatio) is
+ // Use TaskFragment-bounds if available so that activity-level letterbox (maxAspectRatio) is
// included in the animation.
- return task != null ? task.getBounds() : getBounds();
+ final TaskFragment taskFragment = getTaskFragment();
+ return taskFragment != null ? taskFragment.getBounds() : getBounds();
}
@Override
@@ -7970,11 +8344,15 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
final float maxAspectRatio = info.getMaxAspectRatio();
final Task rootTask = getRootTask();
- final float minAspectRatio = info.getMinAspectRatio();
-
+ final float minAspectRatio = getMinAspectRatio();
+ // Not using ActivityRecord#isResizeable() directly because app compatibility testing
+ // showed that android:supportsPictureInPicture="true" alone is not sufficient signal for
+ // not letterboxing an app.
+ // TODO(214602463): Remove multi-window check since orientation and aspect ratio
+ // restrictions should always be applied in multi-window.
if (task == null || rootTask == null
- || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()
- && !fixedOrientationLetterboxed)
+ || (inMultiWindowMode() && isResizeable(/* checkPictureInPictureSupport */ false)
+ && !fixedOrientationLetterboxed)
|| (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
|| isInVrUiMode(getConfiguration())) {
// We don't enforce aspect ratio if the activity task is in multiwindow unless it is in
@@ -8067,7 +8445,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
// If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the
// container app bounds. Otherwise the entire container bounds are available.
if (!outBounds.equals(containingBounds)) {
- // The horizontal position should not cover insets.
+ // The horizontal position should not cover insets (e.g. display cutout).
outBounds.left = containingAppBounds.left;
}
@@ -8075,6 +8453,20 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
/**
+ * Returns the min aspect ratio of this activity.
+ */
+ private float getMinAspectRatio() {
+ return info.getMinAspectRatio(getRequestedOrientation());
+ }
+
+ /**
+ * Returns true if the activity has maximum or minimum aspect ratio.
+ */
+ private boolean hasFixedAspectRatio() {
+ return info.hasFixedAspectRatio(getRequestedOrientation());
+ }
+
+ /**
* Returns the aspect ratio of the given {@code rect}.
*/
static float computeAspectRatio(Rect rect) {
@@ -8225,7 +8617,9 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
configChangeFlags |= changes;
startFreezingScreenLocked(globalChanges);
forceNewConfig = false;
- preserveWindow &= isResizeOnlyChange(changes);
+ // Do not preserve window if it is freezing screen because the original window won't be
+ // able to update drawn state that causes freeze timeout.
+ preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
if (hasResizeChange) {
final boolean isDragResizing = task.isDragResizing();
@@ -8803,6 +9197,10 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
}
proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled());
proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode());
+ proto.write(MIN_ASPECT_RATIO, getMinAspectRatio());
+ // Only record if max bounds sandboxing is applied, if the caller has the necessary
+ // permission to access the device configs.
+ proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds());
}
@Override
@@ -8841,6 +9239,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
* compatibility mode activity compute the configuration without relying on its current display.
*/
static class CompatDisplayInsets {
+ /** The original rotation the compat insets were computed in */
+ final @Rotation int mOriginalRotation;
/** The container width on rotation 0. */
private final int mWidth;
/** The container height on rotation 0. */
@@ -8867,6 +9267,7 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
/** Constructs the environment to simulate the bounds behavior of the given container. */
CompatDisplayInsets(DisplayContent display, ActivityRecord container,
@Nullable Rect fixedOrientationBounds) {
+ mOriginalRotation = display.getRotation();
mIsFloating = container.getWindowConfiguration().tasksAreFloating();
if (mIsFloating) {
final Rect containerBounds = container.getWindowConfiguration().getBounds();
@@ -9013,7 +9414,8 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
outAppBounds.offset(insets.left, insets.top);
} else if (rotation != ROTATION_UNDEFINED) {
// Ensure the app bounds won't overlap with insets.
- Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]);
+ TaskFragment.intersectWithInsetsIfFits(outAppBounds, outBounds,
+ mNonDecorInsets[rotation]);
}
}
}
@@ -9036,17 +9438,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return null;
}
final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- task.getBounds(), Type.systemBars(), false /* ignoreVisibility */);
+ task.getBounds(), Type.systemBars(), false /* ignoreVisibility */).toRect();
InsetUtils.addInsets(insets, getLetterboxInsets());
- return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
- record.mAdapter.mCapturedLeash, !fillsParent(),
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(task.mTaskId,
+ record.getMode(), record.mAdapter.mCapturedLeash, !fillsParent(),
new Rect(), insets,
getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
- record.mAdapter.mRootTaskBounds, task.getWindowConfiguration(),
+ record.mAdapter.mEndBounds, task.getWindowConfiguration(),
false /*isNotInRecents*/,
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
- record.mStartBounds, task.getTaskInfo());
+ record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
+ target.hasAnimatingParent = record.hasAnimatingParent();
+ return target;
}
@Override
@@ -9082,6 +9486,19 @@ public final class ActivityRecord extends WindowToken implements WindowManagerSe
return false;
}
+ @Override
+ void finishSync(Transaction outMergedTransaction, boolean cancel) {
+ // This override is just for getting metrics. allFinished needs to be checked before
+ // finish because finish resets all the states.
+ mLastAllReadyAtSync = allSyncFinished();
+ super.finishSync(outMergedTransaction, cancel);
+ }
+
+ @Override
+ boolean canBeAnimationTarget() {
+ return true;
+ }
+
static class Builder {
private final ActivityTaskManagerService mAtmService;
private WindowProcessController mCallerApp;
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 8540fa7cf347..30c7b232fcc8 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -16,10 +16,10 @@
package com.android.server.wm;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
import android.util.ArraySet;
import android.util.Slog;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index b6f2f243040e..bb5d962760e7 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -27,6 +27,7 @@ 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;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
@@ -38,6 +39,7 @@ 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.UserHandle;
import android.provider.Settings;
@@ -488,6 +490,32 @@ public class ActivityStartController {
return START_SUCCESS;
}
+ /**
+ * 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 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,
+ @Nullable IBinder resultTo, int callingUid, int callingPid) {
+ final ActivityRecord caller =
+ resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null;
+ return obtainStarter(activityIntent, "startActivityInTaskFragment")
+ .setActivityOptions(activityOptions)
+ .setInTaskFragment(taskFragment)
+ .setResultTo(resultTo)
+ .setRequestCode(-1)
+ .setCallingUid(callingUid)
+ .setCallingPid(callingPid)
+ .setUserId(caller != null ? caller.mUserId : mService.getCurrentUserId())
+ .execute();
+ }
+
void registerRemoteAnimationForNextActivityStart(String packageName,
RemoteAnimationAdapter adapter) {
mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 979cea9b1569..223f0be9bbea 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -51,6 +51,7 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppActivity;
@@ -178,7 +179,33 @@ class ActivityStartInterceptor {
// before issuing the work challenge.
return true;
}
- return interceptLockedManagedProfileIfNeeded();
+ if (interceptLockedManagedProfileIfNeeded()) {
+ return true;
+ }
+
+ final SparseArray<ActivityInterceptorCallback> callbacks =
+ mService.getActivityInterceptorCallbacks();
+ final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo =
+ new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
+ mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
+ mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
+ mActivityOptions);
+
+ for (int i = 0; i < callbacks.size(); i++) {
+ final ActivityInterceptorCallback callback = callbacks.valueAt(i);
+ final Intent newIntent = callback.intercept(interceptorInfo);
+ if (newIntent == null) {
+ continue;
+ }
+ mIntent = newIntent;
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags,
+ null /*profilerInfo*/);
+ return true;
+ }
+ return false;
}
private boolean hasCrossProfileAnimation() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6a44bb8062af..6c57bc0904a2 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -23,11 +23,13 @@ import static android.app.ActivityManager.START_CANCELED;
import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_FLAG_ONLY_IF_NEEDED;
+import static android.app.ActivityManager.START_PERMISSION_DENIED;
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.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -58,6 +60,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -69,12 +72,13 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_USER_
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.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -113,7 +117,7 @@ import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Pools.SynchronizedPool;
import android.util.Slog;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.HeavyWeightSwitcherActivity;
@@ -178,6 +182,7 @@ class ActivityStarter {
private int mPreferredWindowingMode;
private Task mInTask;
+ private TaskFragment mInTaskFragment;
@VisibleForTesting
boolean mAddingToTask;
private Task mReuseTask;
@@ -191,7 +196,6 @@ class ActivityStarter {
private Task mTargetTask;
private boolean mMovedToFront;
private boolean mNoAnimation;
- private boolean mKeepCurTransition;
private boolean mAvoidMoveToFront;
private boolean mFrozeTaskList;
private boolean mTransientLaunch;
@@ -345,6 +349,7 @@ class ActivityStarter {
boolean avoidMoveToFront;
ActivityRecord[] outActivity;
Task inTask;
+ TaskFragment inTaskFragment;
String reason;
ProfilerInfo profilerInfo;
Configuration globalConfig;
@@ -395,6 +400,7 @@ class ActivityStarter {
componentSpecified = false;
outActivity = null;
inTask = null;
+ inTaskFragment = null;
reason = null;
profilerInfo = null;
globalConfig = null;
@@ -410,7 +416,7 @@ class ActivityStarter {
/**
* Adopts all values from passed in request.
*/
- void set(Request request) {
+ void set(@NonNull Request request) {
caller = request.caller;
intent = request.intent;
intentGrants = request.intentGrants;
@@ -435,6 +441,7 @@ class ActivityStarter {
componentSpecified = request.componentSpecified;
outActivity = request.outActivity;
inTask = request.inTask;
+ inTaskFragment = request.inTaskFragment;
reason = request.reason;
profilerInfo = request.profilerInfo;
globalConfig = request.globalConfig;
@@ -578,6 +585,7 @@ class ActivityStarter {
mPreferredWindowingMode = starter.mPreferredWindowingMode;
mInTask = starter.mInTask;
+ mInTaskFragment = starter.mInTaskFragment;
mAddingToTask = starter.mAddingToTask;
mReuseTask = starter.mReuseTask;
@@ -589,7 +597,6 @@ class ActivityStarter {
mTargetRootTask = starter.mTargetRootTask;
mMovedToFront = starter.mMovedToFront;
mNoAnimation = starter.mNoAnimation;
- mKeepCurTransition = starter.mKeepCurTransition;
mAvoidMoveToFront = starter.mAvoidMoveToFront;
mFrozeTaskList = starter.mFrozeTaskList;
@@ -741,7 +748,7 @@ class ActivityStarter {
Slog.w(TAG, "Unable to find app for caller " + mRequest.caller + " (pid="
+ mRequest.callingPid + ") when starting: " + mRequest.intent.toString());
SafeActivityOptions.abort(mRequest.activityOptions);
- return ActivityManager.START_PERMISSION_DENIED;
+ return START_PERMISSION_DENIED;
}
}
@@ -839,6 +846,7 @@ class ActivityStarter {
final int startFlags = request.startFlags;
final SafeActivityOptions options = request.activityOptions;
Task inTask = request.inTask;
+ TaskFragment inTaskFragment = request.inTaskFragment;
int err = ActivityManager.START_SUCCESS;
// Pull the optional Ephemeral Installer-only bundle out of the options early.
@@ -854,7 +862,7 @@ class ActivityStarter {
} else {
Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid
+ ") when starting: " + intent.toString());
- err = ActivityManager.START_PERMISSION_DENIED;
+ err = START_PERMISSION_DENIED;
}
}
@@ -868,7 +876,7 @@ class ActivityStarter {
ActivityRecord sourceRecord = null;
ActivityRecord resultRecord = null;
if (resultTo != null) {
- sourceRecord = mRootWindowContainer.isInAnyTask(resultTo);
+ sourceRecord = ActivityRecord.isInAnyTask(resultTo);
if (DEBUG_RESULTS) {
Slog.v(TAG_RESULTS, "Will send result to " + resultTo + " " + sourceRecord);
}
@@ -1179,8 +1187,8 @@ class ActivityStarter {
}
mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
- request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask,
- restrictedBgActivity, intentGrants);
+ request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,
+ inTask, inTaskFragment, restrictedBgActivity, intentGrants);
if (request.outActivity != null) {
request.outActivity[0] = mLastStartActivityRecord;
@@ -1273,7 +1281,7 @@ class ActivityStarter {
// This is used to block background activity launch even if the app is still
// visible to user after user clicking home button.
- final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
+ final int appSwitchState = mService.getBalAppSwitchesState();
// don't abort if the callingUid has a visible window or is a persistent system process
final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
@@ -1286,7 +1294,9 @@ class ActivityStarter {
// Normal apps with visible app window will be allowed to start activity if app switching
// is allowed, or apps like live wallpaper with non app visible window will be allowed.
- if (((appSwitchAllowed || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
+ final boolean appSwitchAllowedOrFg =
+ appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
+ if (((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
&& callingUidHasAnyVisibleWindow)
|| isCallingUidPersistentSystemProcess) {
if (DEBUG_ACTIVITY_STARTS) {
@@ -1396,7 +1406,7 @@ class ActivityStarter {
// don't abort if the callerApp or other processes of that uid are allowed in any way
if (callerApp != null) {
// first check the original calling process
- if (callerApp.areBackgroundActivityStartsAllowed(appSwitchAllowed)) {
+ if (callerApp.areBackgroundActivityStartsAllowed(appSwitchState)) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Background activity start allowed: callerApp process (pid = "
+ callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed");
@@ -1410,7 +1420,7 @@ class ActivityStarter {
for (int i = uidProcesses.size() - 1; i >= 0; i--) {
final WindowProcessController proc = uidProcesses.valueAt(i);
if (proc != callerApp
- && proc.areBackgroundActivityStartsAllowed(appSwitchAllowed)) {
+ && proc.areBackgroundActivityStartsAllowed(appSwitchState)) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG,
"Background activity start allowed: process " + proc.getPid()
@@ -1424,7 +1434,7 @@ class ActivityStarter {
// anything that has fallen through would currently be aborted
Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
+ "; callingUid: " + callingUid
- + "; appSwitchAllowed: " + appSwitchAllowed
+ + "; appSwitchState: " + appSwitchState
+ "; isCallingUidForeground: " + isCallingUidForeground
+ "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow
+ "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class,
@@ -1550,29 +1560,43 @@ class ActivityStarter {
* Here also ensures that the starting activity is removed if the start wasn't successful.
*/
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
- IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
- int startFlags, boolean doResume, ActivityOptions options, Task inTask,
- boolean restrictedBgActivity, NeededUriGrants intentGrants) {
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ int startFlags, boolean doResume, ActivityOptions options, Task inTask,
+ TaskFragment inTaskFragment, boolean restrictedBgActivity,
+ NeededUriGrants intentGrants) {
int result = START_CANCELED;
+ boolean startResultSuccessful = false;
final Task startedActivityRootTask;
// Create a transition now to record the original intent of actions taken within
// startActivityInner. Otherwise, logic in startActivityInner could start a different
// transition based on a sub-action.
// Only do the create here (and defer requestStart) since startActivityInner might abort.
- final Transition newTransition = (!mService.getTransitionController().isCollecting()
- && mService.getTransitionController().getTransitionPlayer() != null)
- ? mService.getTransitionController().createTransition(TRANSIT_OPEN) : null;
- IRemoteTransition remoteTransition = r.takeRemoteTransition();
+ final TransitionController transitionController = r.mTransitionController;
+ Transition newTransition = (!transitionController.isCollecting()
+ && transitionController.getTransitionPlayer() != null)
+ ? transitionController.createTransition(TRANSIT_OPEN) : null;
+ RemoteTransition remoteTransition = r.takeRemoteTransition();
if (newTransition != null && remoteTransition != null) {
newTransition.setRemoteTransition(remoteTransition);
}
- mService.getTransitionController().collect(r);
+ transitionController.collect(r);
+ final boolean isTransient = r.getOptions() != null && r.getOptions().getTransientLaunch();
try {
mService.deferWindowLayout();
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
- startFlags, doResume, options, inTask, restrictedBgActivity, intentGrants);
+ startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
+ intentGrants);
+ startResultSuccessful = ActivityManager.isStartResultSuccessful(result);
+ final boolean taskAlwaysOnTop = options != null && options.getTaskAlwaysOnTop();
+ // Apply setAlwaysOnTop when starting an Activity is successful regardless of creating
+ // a new Activity or recycling the existing Activity.
+ if (taskAlwaysOnTop && startResultSuccessful) {
+ final Task targetRootTask =
+ mTargetRootTask != null ? mTargetRootTask : mTargetTask.getRootTask();
+ targetRootTask.setAlwaysOnTop(true);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
startedActivityRootTask = handleStartResult(r, result);
@@ -1580,7 +1604,7 @@ class ActivityStarter {
mSupervisor.mUserLeaving = false;
// Transition housekeeping
- if (!ActivityManager.isStartResultSuccessful(result)) {
+ if (!startResultSuccessful) {
if (newTransition != null) {
newTransition.abort();
}
@@ -1599,17 +1623,27 @@ class ActivityStarter {
statusBar.collapsePanels();
}
}
- if (result == START_SUCCESS || result == START_TASK_TO_FRONT) {
+ final boolean started = result == START_SUCCESS || result == START_TASK_TO_FRONT;
+ if (started) {
// The activity is started new rather than just brought forward, so record
// it as an existence change.
- mService.getTransitionController().collectExistenceChange(r);
+ transitionController.collectExistenceChange(r);
+ } else if (result == START_DELIVERED_TO_TOP && newTransition != null) {
+ // We just delivered to top, so there isn't an actual transition here
+ newTransition.abort();
+ newTransition = null;
+ }
+ if (isTransient) {
+ // `r` isn't guaranteed to be the actual relevant activity, so we must wait
+ // until after we launched to identify the relevant activity.
+ transitionController.setTransientLaunch(mLastStartActivityRecord);
}
if (newTransition != null) {
- mService.getTransitionController().requestStartTransition(newTransition,
+ transitionController.requestStartTransition(newTransition,
mTargetTask, remoteTransition);
- } else {
+ } else if (started) {
// Make the collecting transition wait until this request is ready.
- mService.getTransitionController().setReady(false);
+ transitionController.setReady(r, false);
}
}
}
@@ -1669,15 +1703,15 @@ class ActivityStarter {
*
* Note: This method should only be called from {@link #startActivityUnchecked}.
*/
-
// TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
@VisibleForTesting
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, Task inTask,
- boolean restrictedBgActivity, NeededUriGrants intentGrants) {
- setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
- voiceInteractor, restrictedBgActivity);
+ TaskFragment inTaskFragment, boolean restrictedBgActivity,
+ NeededUriGrants intentGrants) {
+ setInitialState(r, options, inTask, inTaskFragment, doResume, startFlags, sourceRecord,
+ voiceSession, voiceInteractor, restrictedBgActivity);
computeLaunchingTaskFlags();
@@ -1685,6 +1719,8 @@ class ActivityStarter {
mIntent.setFlags(mLaunchFlags);
+ // Get top task at beginning because the order may be changed when reusing existing task.
+ final Task prevTopTask = mPreferredTaskDisplayArea.getFocusedRootTask();
final Task reusedTask = getReusableTask();
// If requested, freeze the task list
@@ -1759,11 +1795,6 @@ class ActivityStarter {
if (!mAvoidMoveToFront && mDoResume) {
mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
- if (mOptions != null) {
- if (mOptions.getTaskAlwaysOnTop()) {
- mTargetRootTask.setAlwaysOnTop(true);
- }
- }
if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.mInternal.isDreaming()) {
// Launching underneath dream activity (fullscreen, always-on-top). Run the launch-
// -behind transition so the Activity gets created and starts in visible state.
@@ -1785,24 +1816,23 @@ class ActivityStarter {
UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
resultToUid /*visible*/, true /*direct*/);
}
+ final Task startedTask = mStartActivity.getTask();
if (newTask) {
- EventLogTags.writeWmCreateTask(mStartActivity.mUserId,
- mStartActivity.getTask().mTaskId);
+ EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId);
}
- mStartActivity.logStartActivity(
- EventLogTags.WM_CREATE_ACTIVITY, mStartActivity.getTask());
+ mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask);
- mTargetRootTask.mLastPausedActivity = null;
+ mStartActivity.getTaskFragment().clearLastPausedActivity();
mRootWindowContainer.startPowerModeLaunchIfNeeded(
false /* forceSend */, mStartActivity);
+ final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded();
mTargetRootTask.startActivityLocked(mStartActivity,
topRootTask != null ? topRootTask.getTopNonFinishingActivity() : null, newTask,
- mKeepCurTransition, mOptions, sourceRecord);
+ isTaskSwitch, mOptions, sourceRecord);
if (mDoResume) {
- final ActivityRecord topTaskActivity =
- mStartActivity.getTask().topRunningActivityLocked();
+ final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked();
if (!mTargetRootTask.isTopActivityFocusable()
|| (topTaskActivity != null && topTaskActivity.isTaskOverlay()
&& mStartActivity != topTaskActivity)) {
@@ -1836,8 +1866,8 @@ class ActivityStarter {
mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);
// Update the recent tasks list immediately when the activity starts
- mSupervisor.mRecentTasks.add(mStartActivity.getTask());
- mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(),
+ mSupervisor.mRecentTasks.add(startedTask);
+ mSupervisor.handleNonResizableTaskIfNeeded(startedTask,
mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask);
return START_SUCCESS;
@@ -1951,10 +1981,46 @@ class ActivityStarter {
}
}
+ if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) {
+ Slog.e(TAG, "Permission denied: Cannot embed " + r + " to " + mInTaskFragment.getTask()
+ + " targetTask= " + targetTask);
+ return START_PERMISSION_DENIED;
+ }
+
return START_SUCCESS;
}
/**
+ * Return {@code true} if an activity can be embedded to the TaskFragment.
+ * @param taskFragment the TaskFragment for embedding.
+ * @param starting the starting activity.
+ * @param newTask whether the starting activity is going to be launched on a new task.
+ * @param targetTask the target task for launching activity, which could be different from
+ * the one who hosting the embedding.
+ */
+ private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, ActivityRecord starting,
+ boolean newTask, Task targetTask) {
+ final Task hostTask = taskFragment.getTask();
+ if (hostTask == null) {
+ return false;
+ }
+
+ // Allowing the embedding if the task is owned by system.
+ final int hostUid = hostTask.effectiveUid;
+ if (UserHandle.getAppId(hostUid) == Process.SYSTEM_UID) {
+ return true;
+ }
+
+ // Not allowed embedding an activity of another app.
+ if (hostUid != starting.getUid()) {
+ return false;
+ }
+
+ // Not allowed embedding task.
+ return !newTask && (targetTask == null || targetTask == hostTask);
+ }
+
+ /**
* Prepare the target task to be reused for this launch, which including:
* - Position the target task on valid root task on preferred display.
* - Comply to the specified activity launch flags
@@ -2043,7 +2109,7 @@ class ActivityStarter {
// We didn't do anything... but it was needed (a.k.a., client don't use that intent!)
// And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetRootTaskIfNeeded();
-
+
mLastStartActivityRecord = targetTaskTop;
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
@@ -2069,7 +2135,7 @@ class ActivityStarter {
}
// For paranoia, make sure we have correctly resumed the top activity.
- topRootTask.mLastPausedActivity = null;
+ top.getTaskFragment().clearLastPausedActivity();
if (mDoResume) {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
@@ -2165,7 +2231,7 @@ class ActivityStarter {
task.moveActivityToFrontLocked(act);
act.updateOptionsLocked(mOptions);
deliverNewIntent(act, intentGrants);
- mTargetRootTask.mLastPausedActivity = null;
+ act.getTaskFragment().clearLastPausedActivity();
} else {
mAddingToTask = true;
}
@@ -2233,6 +2299,7 @@ class ActivityStarter {
mPreferredWindowingMode = WINDOWING_MODE_UNDEFINED;
mInTask = null;
+ mInTaskFragment = null;
mAddingToTask = false;
mReuseTask = null;
@@ -2244,7 +2311,6 @@ class ActivityStarter {
mTargetTask = null;
mMovedToFront = false;
mNoAnimation = false;
- mKeepCurTransition = false;
mAvoidMoveToFront = false;
mFrozeTaskList = false;
mTransientLaunch = false;
@@ -2260,9 +2326,9 @@ class ActivityStarter {
}
private void setInitialState(ActivityRecord r, ActivityOptions options, Task inTask,
- boolean doResume, int startFlags, ActivityRecord sourceRecord,
- IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
- boolean restrictedBgActivity) {
+ TaskFragment inTaskFragment, boolean doResume, int startFlags,
+ ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession,
+ IVoiceInteractor voiceInteractor, boolean restrictedBgActivity) {
reset(false /* clearRequest */);
mStartActivity = r;
@@ -2325,7 +2391,7 @@ class ActivityStarter {
// of this in the record so that we can skip it when trying to find
// the top running activity.
mDoResume = doResume;
- if (!doResume || !r.okToShowLocked() || mLaunchTaskBehind) {
+ if (!doResume || !r.showToCurrentUser() || mLaunchTaskBehind) {
r.delayedResume = true;
mDoResume = false;
}
@@ -2352,6 +2418,11 @@ class ActivityStarter {
}
mTransientLaunch = mOptions.getTransientLaunch();
mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
+
+ if (inTaskFragment == null) {
+ inTaskFragment = TaskFragment.fromTaskFragmentToken(
+ mOptions.getLaunchTaskFragmentToken(), mService);
+ }
}
mNotTop = (mLaunchFlags & FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? sourceRecord : null;
@@ -2365,6 +2436,7 @@ class ActivityStarter {
Slog.w(TAG, "Starting activity in task not in recents: " + inTask);
mInTask = null;
}
+ mInTaskFragment = inTaskFragment;
mStartFlags = startFlags;
// If the onlyIfNeeded flag is set, then we can do this if the activity being launched
@@ -2588,13 +2660,28 @@ class ActivityStarter {
/**
* Figure out which task and activity to bring to front when we have found an existing matching
* activity record in history. May also clear the task if needed.
+ *
* @param intentActivity Existing matching activity.
* @return {@link ActivityRecord} brought to front.
*/
private void setTargetRootTaskIfNeeded(ActivityRecord intentActivity) {
- mTargetRootTask = intentActivity.getRootTask();
- mTargetRootTask.mLastPausedActivity = null;
+ intentActivity.getTaskFragment().clearLastPausedActivity();
Task intentTask = intentActivity.getTask();
+
+ // Only update the target-root-task when it is not indicated.
+ if (mTargetRootTask == null) {
+ if (mSourceRecord != null && mSourceRecord.mLaunchRootTask != null) {
+ // Inherit the target-root-task from source to ensure trampoline activities will be
+ // launched into the same root task.
+ mTargetRootTask = Task.fromWindowContainerToken(mSourceRecord.mLaunchRootTask);
+ } else {
+ final Task launchRootTask =
+ getLaunchRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions);
+ mTargetRootTask =
+ launchRootTask != null ? launchRootTask : intentActivity.getRootTask();
+ }
+ }
+
// If the target task is not in the front, then we need to bring it to the front...
// except... well, with SINGLE_TASK_LAUNCH it's not entirely clear. We'd like to have
// the same behavior as if a new instance was being started, which means not bringing it
@@ -2622,16 +2709,14 @@ class ActivityStarter {
intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
}
- final Task launchRootTask = getLaunchRootTask(mStartActivity, mLaunchFlags,
- intentTask, mOptions);
- if (launchRootTask == null || launchRootTask == mTargetRootTask) {
+ if (mTargetRootTask == intentActivity.getRootTask()) {
// TODO(b/151572268): Figure out a better way to move tasks in above 2-levels
// tasks hierarchies.
if (mTargetRootTask != intentTask
&& mTargetRootTask != intentTask.getParent().asTask()) {
intentTask.getParent().positionChildAt(POSITION_TOP, intentTask,
false /* includingParents */);
- intentTask = intentTask.getParent().asTask();
+ intentTask = intentTask.getParent().asTaskFragment().getTask();
}
// If the task is in multi-windowing mode, the activity may already be on
// the top (visible to user but not the global top), then the result code
@@ -2647,8 +2732,12 @@ class ActivityStarter {
mStartActivity.appTimeTracker, DEFER_RESUME,
"bringingFoundTaskToFront");
mMovedToFront = !wasTopOfVisibleRootTask;
- } else {
- intentTask.reparent(launchRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+ } else if (intentActivity.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ // Leaves reparenting pinned task operations to task organizer to make sure it
+ // dismisses pinned task properly.
+ // TODO(b/199997762): Consider leaving all reparent operation of organized tasks
+ // to task organizer.
+ intentTask.reparent(mTargetRootTask, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
ANIMATE, DEFER_RESUME, "reparentToTargetRootTask");
mMovedToFront = true;
}
@@ -2671,6 +2760,19 @@ class ActivityStarter {
mTargetRootTask = intentActivity.getRootTask();
mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED,
mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask);
+
+ // We need to check if there is a launch root task in TDA for this target root task.
+ // If it exist, we need to reparent target root task from TDA to launch root task.
+ final TaskDisplayArea tda = mTargetRootTask.getDisplayArea();
+ final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(),
+ mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask,
+ mLaunchFlags);
+ // If target root task is created by organizer, let organizer handle reparent itself.
+ if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null
+ && launchRootTask != mTargetRootTask) {
+ mTargetRootTask.reparent(launchRootTask, POSITION_TOP);
+ mTargetRootTask = launchRootTask;
+ }
}
private void resumeTargetRootTaskIfNeeded() {
@@ -2698,7 +2800,7 @@ class ActivityStarter {
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
- mService.getTransitionController().collectExistenceChange(task);
+ task.mTransitionController.collectExistenceChange(task);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
ProtoLog.v(WM_DEBUG_TASKS, "Starting new activity %s in new task %s",
@@ -2720,7 +2822,7 @@ class ActivityStarter {
mIntentDelivered = true;
}
- private void addOrReparentStartingActivity(Task parent, String reason) {
+ private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
String packageName= mService.mContext.getPackageName();
if (mPerf != null) {
if (mPerf.getPerfHalVersion() >= BoostFramework.PERF_HAL_V23) {
@@ -2737,13 +2839,50 @@ class ActivityStarter {
packageName, -1, BoostFramework.Launch.BOOST_V1);
}
}
- if (mStartActivity.getTask() == null || mStartActivity.getTask() == parent) {
- parent.addChild(mStartActivity);
+ TaskFragment newParent = task;
+ if (mInTaskFragment != null) {
+ // mInTaskFragment is created and added to the leaf task by task fragment organizer's
+ // request. If the task was resolved and different than mInTaskFragment, reparent the
+ // task to mInTaskFragment for embedding.
+ if (mInTaskFragment.getTask() != task) {
+ if (shouldReparentInTaskFragment(task)) {
+ task.reparent(mInTaskFragment, POSITION_TOP);
+ }
+ } else {
+ newParent = mInTaskFragment;
+ }
+ } else {
+ final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
+ false /* includingEmbeddedTask */);
+ final TaskFragment taskFragment = top != null ? top.getTaskFragment() : null;
+ if (taskFragment != null && taskFragment.isEmbedded()
+ && canEmbedActivity(taskFragment, mStartActivity, false /* newTask */, task)) {
+ // Use the embedded TaskFragment of the top activity as the new parent if the
+ // activity can be embedded.
+ newParent = top.getTaskFragment();
+ }
+ }
+
+ if (mStartActivity.getTaskFragment() == null
+ || mStartActivity.getTaskFragment() == newParent) {
+ newParent.addChild(mStartActivity, POSITION_TOP);
} else {
- mStartActivity.reparent(parent, parent.getChildCount() /* top */, reason);
+ mStartActivity.reparent(newParent, newParent.getChildCount() /* top */, reason);
}
}
+ private boolean shouldReparentInTaskFragment(Task task) {
+ // The task has not been embedded. We should reparent the task to TaskFragment.
+ if (!task.isEmbedded()) {
+ return true;
+ }
+ WindowContainer<?> parent = task.getParent();
+ // If the Activity is going to launch on top of embedded Task in the same TaskFragment,
+ // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to
+ // another TaskFragment.
+ return parent.asTaskFragment() != mInTaskFragment;
+ }
+
private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance,
boolean launchSingleTask, int launchFlags) {
if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
@@ -2972,6 +3111,11 @@ class ActivityStarter {
return this;
}
+ ActivityStarter setInTaskFragment(TaskFragment taskFragment) {
+ mRequest.inTaskFragment = taskFragment;
+ return this;
+ }
+
ActivityStarter setWaitResult(WaitResult result) {
mRequest.waitResult = result;
return this;
@@ -3055,5 +3199,7 @@ class ActivityStarter {
pw.print(mDoResume);
pw.print(" mAddingToTask=");
pw.println(mAddingToTask);
+ pw.print(" mInTaskFragment=");
+ pw.println(mInTaskFragment);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 1759cdeb60d7..3150ccd86d8c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.RemoteException;
import android.service.voice.IVoiceInteractionSession;
import android.util.IntArray;
@@ -595,11 +596,56 @@ public abstract class ActivityTaskManagerInternal {
public abstract boolean isBaseOfLockedTask(String packageName);
/**
- * Create an interface to update configuration for an application.
+ * Creates an interface to update configuration for the calling application.
*/
public abstract PackageConfigurationUpdater createPackageConfigurationUpdater();
/**
+ * Creates an interface to update configuration for an arbitrary application specified by it's
+ * packageName and userId.
+ */
+ public abstract PackageConfigurationUpdater createPackageConfigurationUpdater(
+ String packageName, int userId);
+
+ /**
+ * Retrieves and returns the app-specific configuration for an arbitrary application specified
+ * by its packageName and userId. Returns null if no app-specific configuration has been set.
+ */
+ @Nullable
+ public abstract PackageConfig getApplicationConfig(String packageName,
+ int userId);
+
+ /**
+ * Holds app-specific configurations.
+ */
+ public static class PackageConfig {
+ /**
+ * nightMode for the application, null if app-specific nightMode is not set.
+ */
+ @Nullable
+ public final Integer mNightMode;
+
+ /**
+ * {@link LocaleList} for the application, null if app-specific locales are not set.
+ */
+ @Nullable
+ public final LocaleList mLocales;
+
+ PackageConfig(Integer nightMode, LocaleList locales) {
+ mNightMode = nightMode;
+ mLocales = locales;
+ }
+
+ /**
+ * Returns the string representation of the app-specific configuration.
+ */
+ @Override
+ public String toString() {
+ return "PackageConfig: nightMode " + mNightMode + " locales " + mLocales;
+ }
+ }
+
+ /**
* An interface to update configuration for an application, and will persist override
* configuration for this package.
*/
@@ -611,6 +657,14 @@ public abstract class ActivityTaskManagerInternal {
PackageConfigurationUpdater setNightMode(int nightMode);
/**
+ * Sets the app-specific locales for the application referenced by this updater.
+ * This setting is persisted and will overlay on top of the system locales for
+ * the said application.
+ * @return the current {@link PackageConfigurationUpdater} updated with the provided locale.
+ */
+ PackageConfigurationUpdater setLocales(LocaleList locales);
+
+ /**
* Commit changes.
*/
void commit();
@@ -621,4 +675,15 @@ public abstract class ActivityTaskManagerInternal {
*/
public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
String callingPackage);
+
+ /** Called when the device is waking up */
+ public abstract void notifyWakingUp();
+
+ /**
+ * Registers a callback which can intercept activity starts.
+ * @throws IllegalArgumentException if duplicate ids are provided
+ */
+ public abstract void registerActivityStartInterceptor(
+ @ActivityInterceptorCallback.OrderedId int id,
+ ActivityInterceptorCallback callback);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ca4c1257323a..8726f4ff5a12 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -64,6 +64,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_WAKE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -91,6 +92,8 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.Scr
import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE;
import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen;
import static com.android.server.am.EventLogTags.writeConfigurationChanged;
+import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -457,6 +460,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/** The controller for all operations related to locktask. */
private LockTaskController mLockTaskController;
private ActivityStartController mActivityStartController;
+ private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks =
+ new SparseArray<>();
PackageConfigPersister mPackageConfigPersister;
boolean mSuppressResizeConfigChanges;
@@ -499,7 +504,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* Whether normal application switches are allowed; a call to {@link #stopAppSwitches()
* disables this.
*/
- private volatile boolean mAppSwitchesAllowed = true;
+ private volatile int mAppSwitchesState = APP_SWITCH_ALLOW;
+
+ // The duration of resuming foreground app switch from disallow.
+ private static final long RESUME_FG_APP_SWITCH_MS = 500;
+
+ /** App switch is not allowed. */
+ static final int APP_SWITCH_DISALLOW = 0;
+
+ /** App switch is allowed only if the activity launch was requested by a foreground app. */
+ static final int APP_SWITCH_FG_ONLY = 1;
+
+ /** App switch is allowed. */
+ static final int APP_SWITCH_ALLOW = 2;
+
+ @IntDef({
+ APP_SWITCH_DISALLOW,
+ APP_SWITCH_FG_ONLY,
+ APP_SWITCH_ALLOW,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface AppSwitchState {}
/**
* Last stop app switches time, apps finished before this time cannot start background activity
@@ -652,16 +677,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
*/
volatile int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
+ /** Whether to keep higher priority to launch app while device is sleeping. */
+ private volatile boolean mRetainPowerModeAndTopProcessState;
+
+ /** The timeout to restore power mode if {@link #mRetainPowerModeAndTopProcessState} is set. */
+ private static final long POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS = 1000;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({
POWER_MODE_REASON_START_ACTIVITY,
POWER_MODE_REASON_FREEZE_DISPLAY,
+ POWER_MODE_REASON_UNKNOWN_VISIBILITY,
POWER_MODE_REASON_ALL,
})
@interface PowerModeReason {}
static final int POWER_MODE_REASON_START_ACTIVITY = 1 << 0;
static final int POWER_MODE_REASON_FREEZE_DISPLAY = 1 << 1;
+ /** @see UnknownAppVisibilityController */
+ static final int POWER_MODE_REASON_UNKNOWN_VISIBILITY = 1 << 2;
/** This can only be used by {@link #endLaunchPowerMode(int)}.*/
static final int POWER_MODE_REASON_ALL = (1 << 2) - 1;
@@ -732,6 +766,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
WindowOrganizerController mWindowOrganizerController;
TaskOrganizerController mTaskOrganizerController;
+ TaskFragmentOrganizerController mTaskFragmentOrganizerController;
@Nullable
private BackgroundActivityStartCallback mBackgroundActivityStartCallback;
@@ -805,6 +840,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
mWindowOrganizerController = new WindowOrganizerController(this);
mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
+ mTaskFragmentOrganizerController =
+ mWindowOrganizerController.mTaskFragmentOrganizerController;
}
public void onSystemReady() {
@@ -943,7 +980,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
setRecentTasks(new RecentTasks(this, mTaskSupervisor));
mVrController = new VrController(mGlobalLock);
mKeyguardController = mTaskSupervisor.getKeyguardController();
- mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue);
+ mPackageConfigPersister = new PackageConfigPersister(mTaskSupervisor.mPersisterQueue, this);
}
public void onActivityManagerInternalAdded() {
@@ -974,6 +1011,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
synchronized (mGlobalLock) {
mWindowManager = wm;
mRootWindowContainer = wm.mRoot;
+ mWindowOrganizerController.setWindowManager(wm);
mTempConfig.setToDefaults();
mTempConfig.setLocales(LocaleList.getDefault());
mConfigurationSeq = mTempConfig.seq = 1;
@@ -1102,6 +1140,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return mBackgroundActivityStartCallback;
}
+ SparseArray<ActivityInterceptorCallback> getActivityInterceptorCallbacks() {
+ return mActivityInterceptorCallbacks;
+ }
+
private void start() {
LocalServices.addService(ActivityTaskManagerInternal.class, mInternal);
}
@@ -1222,10 +1264,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// If this is coming from the currently resumed activity, it is
// effectively saying that app switches are allowed at this point.
final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
- if (topFocusedRootTask != null && topFocusedRootTask.getResumedActivity() != null
- && topFocusedRootTask.getResumedActivity().info.applicationInfo.uid
+ if (topFocusedRootTask != null && topFocusedRootTask.getTopResumedActivity() != null
+ && topFocusedRootTask.getTopResumedActivity().info.applicationInfo.uid
== Binder.getCallingUid()) {
- mAppSwitchesAllowed = true;
+ mAppSwitchesState = APP_SWITCH_ALLOW;
}
}
return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
@@ -1537,7 +1579,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
sourceToken = resultTo;
}
- sourceRecord = mRootWindowContainer.isInAnyTask(sourceToken);
+ sourceRecord = ActivityRecord.isInAnyTask(sourceToken);
if (sourceRecord == null) {
throw new SecurityException("Called with bad activity token: " + sourceToken);
}
@@ -1719,10 +1761,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(bOptions);
final long origId = Binder.clearCallingIdentity();
try {
- synchronized (mGlobalLock) {
- return mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, taskId,
- safeOptions);
- }
+ return mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, taskId,
+ safeOptions);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -1881,25 +1921,42 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public void setFocusedTask(int taskId) {
enforceTaskPermission("setFocusedTask()");
- ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d", taskId);
final long callingId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- final Task task = mRootWindowContainer.anyTaskForId(taskId,
- MATCH_ATTACHED_TASK_ONLY);
- if (task == null) {
- return;
- }
- final ActivityRecord r = task.topRunningActivityLocked();
- if (r != null && r.moveFocusableActivityToTop("setFocusedTask")) {
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
+ setFocusedTask(taskId, null /* touchedActivity */);
}
} finally {
Binder.restoreCallingIdentity(callingId);
}
}
+ void setFocusedTask(int taskId, ActivityRecord touchedActivity) {
+ ProtoLog.d(WM_DEBUG_FOCUS, "setFocusedTask: taskId=%d touchedActivity=%s", taskId,
+ touchedActivity);
+ final Task task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_ONLY);
+ if (task == null) {
+ return;
+ }
+ final ActivityRecord r = task.topRunningActivityLocked();
+ if (r == null) {
+ return;
+ }
+
+ if (r.moveFocusableActivityToTop("setFocusedTask")) {
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+ } else if (touchedActivity != null && touchedActivity.isFocusable()) {
+ final TaskFragment parent = touchedActivity.getTaskFragment();
+ if (parent != null && parent.isEmbedded()) {
+ // Set the focused app directly if the focused window is currently embedded
+ final DisplayContent displayContent = touchedActivity.getDisplayContent();
+ displayContent.setFocusedApp(touchedActivity);
+ mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /* updateInputWindows */);
+ }
+ }
+ }
+
@Override
public boolean removeTask(int taskId) {
mAmInternal.enforceCallingPermission(REMOVE_TASKS, "removeTask()");
@@ -2121,8 +2178,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* Return true if app switching is allowed.
*/
- boolean getBalAppSwitchesAllowed() {
- return mAppSwitchesAllowed;
+ @AppSwitchState int getBalAppSwitchesState() {
+ return mAppSwitchesState;
}
/** Register an {@link AnrController} to control the ANR dialog behavior */
@@ -3431,7 +3488,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public IWindowOrganizerController getWindowOrganizerController() {
- enforceTaskPermission("getWindowOrganizerController()");
return mWindowOrganizerController;
}
@@ -3644,8 +3700,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void stopAppSwitches() {
mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches");
synchronized (mGlobalLock) {
- mAppSwitchesAllowed = false;
+ mAppSwitchesState = APP_SWITCH_DISALLOW;
mLastStopAppSwitchesTime = SystemClock.uptimeMillis();
+ mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);
+ mH.sendEmptyMessageDelayed(H.RESUME_FG_APP_SWITCH_MSG, RESUME_FG_APP_SWITCH_MS);
}
}
@@ -3653,7 +3711,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public void resumeAppSwitches() {
mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "resumeAppSwitches");
synchronized (mGlobalLock) {
- mAppSwitchesAllowed = true;
+ mAppSwitchesState = APP_SWITCH_ALLOW;
+ mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);
}
}
@@ -3775,6 +3834,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ @Override
+ public void detachNavigationBarFromApp(@NonNull IBinder transition) {
+ mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+ "detachNavigationBarFromApp");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ getTransitionController().legacyDetachNavigationBarFromApp(transition);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
void dumpLastANRLocked(PrintWriter pw) {
pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
if (mLastANRState == null) {
@@ -4038,6 +4111,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
+ // Window configuration is unrelated to persistent configuration (e.g. font scale,
+ // locale). Unset it to avoid affecting the current display configuration.
+ values.windowConfiguration.setToDefaults();
updateConfigurationLocked(values, null, false, true, userId,
false /* deferResume */);
}
@@ -4217,15 +4293,39 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
void startLaunchPowerMode(@PowerModeReason int reason) {
- if (mPowerManagerInternal == null) return;
- mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true);
+ if (mPowerManagerInternal != null) {
+ mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true);
+ }
mLaunchPowerModeReasons |= reason;
+ if ((reason & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) {
+ if (mRetainPowerModeAndTopProcessState) {
+ mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG);
+ }
+ mRetainPowerModeAndTopProcessState = true;
+ mH.sendEmptyMessageDelayed(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG,
+ POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS);
+ Slog.d(TAG, "Temporarily retain top process state for launching app");
+ }
}
void endLaunchPowerMode(@PowerModeReason int reason) {
- if (mPowerManagerInternal == null || mLaunchPowerModeReasons == 0) return;
+ if (mLaunchPowerModeReasons == 0) return;
mLaunchPowerModeReasons &= ~reason;
- if (mLaunchPowerModeReasons == 0) {
+
+ if ((mLaunchPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) {
+ boolean allResolved = true;
+ for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
+ allResolved &= mRootWindowContainer.getChildAt(i).mUnknownAppVisibilityController
+ .allResolved();
+ }
+ if (allResolved) {
+ mLaunchPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY;
+ mRetainPowerModeAndTopProcessState = false;
+ mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG);
+ }
+ }
+
+ if (mLaunchPowerModeReasons == 0 && mPowerManagerInternal != null) {
mPowerManagerInternal.setPowerMode(Mode.LAUNCH, false);
}
}
@@ -4676,7 +4776,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mContext.getText(R.string.heavy_weight_notification_detail))
.setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT
- | PendingIntent.FLAG_IMMUTABLE, null,
+ | PendingIntent.FLAG_IMMUTABLE, null,
new UserHandle(userId)))
.build();
try {
@@ -5061,9 +5161,39 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
process.registerDisplayAreaConfigurationListener(imeContainer);
}
+ @Override
+ public void setRunningRemoteTransitionDelegate(IApplicationThread caller) {
+ mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+ "setRunningRemoteTransition");
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ synchronized (mGlobalLock) {
+ // Also only allow a process which is already runningRemoteAnimation to mark another
+ // process.
+ final WindowProcessController callingProc = getProcessController(callingPid,
+ callingUid);
+ if (callingProc == null || !callingProc.isRunningRemoteTransition()) {
+ final String msg = "Can't call setRunningRemoteTransition from a process (pid="
+ + callingPid + " uid=" + callingUid + ") which isn't itself running a "
+ + "remote transition.";
+ Slog.e(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ final WindowProcessController wpc = getProcessController(caller);
+ if (wpc == null) {
+ Slog.w(TAG, "Unable to find process for application " + caller);
+ return;
+ }
+ wpc.setRunningRemoteAnimation(true /* running */);
+ callingProc.addRemoteAnimationDelegate(wpc);
+ }
+ }
+
final class H extends Handler {
static final int REPORT_TIME_TRACKER_MSG = 1;
static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
+ static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
+ static final int RESUME_FG_APP_SWITCH_MSG = 4;
static final int FIRST_ACTIVITY_TASK_MSG = 100;
static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5087,6 +5217,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
break;
+ case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
+ synchronized (mGlobalLock) {
+ mRetainPowerModeAndTopProcessState = false;
+ endLaunchPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY);
+ if (mTopApp != null
+ && mTopProcessState == ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+ // Restore the scheduling group for sleeping.
+ mTopApp.updateProcessInfo(false /* updateServiceConnection */,
+ false /* activityChange */, true /* updateOomAdj */,
+ false /* addPendingTopUid */);
+ }
+ }
+ }
+ break;
+ case RESUME_FG_APP_SWITCH_MSG: {
+ synchronized (mGlobalLock) {
+ if (mAppSwitchesState == APP_SWITCH_DISALLOW) {
+ mAppSwitchesState = APP_SWITCH_FG_ONLY;
+ }
+ }
+ }
+ break;
}
}
}
@@ -5405,6 +5557,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
public int getTopProcessState() {
+ if (mRetainPowerModeAndTopProcessState) {
+ // There is a launching app while device may be sleeping, force the top state so
+ // the launching process can have top-app scheduling group.
+ return ActivityManager.PROCESS_STATE_TOP;
+ }
return mTopProcessState;
}
@@ -6442,7 +6599,22 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public PackageConfigurationUpdater createPackageConfigurationUpdater() {
- return new PackageConfigurationUpdaterImpl(Binder.getCallingPid());
+ return new PackageConfigurationUpdaterImpl(Binder.getCallingPid(),
+ ActivityTaskManagerService.this);
+ }
+
+ @Override
+ public PackageConfigurationUpdater createPackageConfigurationUpdater(
+ String packageName , int userId) {
+ return new PackageConfigurationUpdaterImpl(packageName, userId,
+ ActivityTaskManagerService.this);
+ }
+
+ @Override
+ @Nullable
+ public ActivityTaskManagerInternal.PackageConfig getApplicationConfig(String packageName,
+ int userId) {
+ return mPackageConfigPersister.findPackageConfiguration(packageName, userId);
}
@Override
@@ -6451,44 +6623,29 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return ActivityTaskManagerService.this.hasSystemAlertWindowPermission(callingUid,
callingPid, callingPackage);
}
- }
-
- final class PackageConfigurationUpdaterImpl implements
- ActivityTaskManagerInternal.PackageConfigurationUpdater {
- private final int mPid;
- private int mNightMode;
-
- PackageConfigurationUpdaterImpl(int pid) {
- mPid = pid;
- }
@Override
- public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
- mNightMode = nightMode;
- return this;
+ public void notifyWakingUp() {
+ // Start a transition for waking. This is needed for showWhenLocked activities.
+ getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */,
+ null /* trigger */, mRootWindowContainer.getDefaultDisplay());
}
@Override
- public void commit() {
+ public void registerActivityStartInterceptor(
+ @ActivityInterceptorCallback.OrderedId int id,
+ ActivityInterceptorCallback callback) {
synchronized (mGlobalLock) {
- final long ident = Binder.clearCallingIdentity();
- try {
- final WindowProcessController wpc = mProcessMap.getProcess(mPid);
- if (wpc == null) {
- Slog.w(TAG, "Override application configuration: cannot find pid " + mPid);
- return;
- }
- wpc.setOverrideNightMode(mNightMode);
- wpc.updateNightModeForAllActivities(mNightMode);
- mPackageConfigPersister.updateFromImpl(wpc.mName, wpc.mUserId, this);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ if (mActivityInterceptorCallbacks.contains(id)) {
+ throw new IllegalArgumentException("Duplicate id provided: " + id);
+ }
+ if (id > LAST_ORDERED_ID || id < FIRST_ORDERED_ID) {
+ throw new IllegalArgumentException(
+ "Provided id " + id + " is not in range of valid ids ["
+ + FIRST_ORDERED_ID + "," + LAST_ORDERED_ID + "]");
}
+ mActivityInterceptorCallbacks.put(id, callback);
}
}
-
- int getNightMode() {
- return mNightMode;
- }
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 92da025ebb49..6be88c7c8fa0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.Manifest.permission.ACTIVITY_EMBEDDING;
import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
@@ -44,11 +45,14 @@ import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.Process.INVALID_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CLOSE;
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.server.wm.ActivityRecord.State.DESTROYED;
+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;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
@@ -73,9 +77,6 @@ import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_R
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
import static com.android.server.wm.Task.TAG_CLEANUP;
@@ -143,7 +144,6 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
-import com.android.internal.os.TransferPipe;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -151,10 +151,10 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.UserState;
+import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -369,6 +369,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
*/
private int mVisibilityTransactionDepth;
+ /**
+ * Whether to the visibility updates that started from {@code RootWindowContainer} should be
+ * deferred.
+ */
+ private boolean mDeferRootVisibilityUpdate;
+
private ActivityMetricsLogger mActivityMetricsLogger;
/** Check if placing task or activity on specified display is allowed. */
@@ -883,6 +889,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
logIfTransactionTooLarge(r.intent, r.getSavedState());
+ if (r.isEmbedded()) {
+ // Sending TaskFragmentInfo to client to ensure the info is updated before
+ // the activity creation.
+ mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(
+ r.getOrganizedTaskFragment());
+ }
// Create activity launch transaction.
final ClientTransaction clientTransaction = ClientTransaction.obtain(
@@ -1363,8 +1375,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// us, we can now deliver.
r.idle = true;
- //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
-
// Check if able to finish booting when device is booting and all resumed activities
// are idle.
if ((mService.isBooting() && mRootWindowContainer.allResumedActivitiesIdle())
@@ -1397,14 +1407,21 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Atomically retrieve all of the other things to do.
processStoppingAndFinishingActivities(r, processPausingActivities, "idle");
+ if (DEBUG_IDLE) {
+ Slogf.i(TAG, "activityIdleInternal(): r=%s, booting=%b, mStartingUsers=%s", r, booting,
+ mStartingUsers);
+ }
+
if (!mStartingUsers.isEmpty()) {
final ArrayList<UserState> startingUsers = new ArrayList<>(mStartingUsers);
mStartingUsers.clear();
-
- if (!booting) {
+ // TODO(b/190854171): remove the isHeadlessSystemUserMode() check on master
+ if (!booting || UserManager.isHeadlessSystemUserMode()) {
// Complete user switch.
for (int i = 0; i < startingUsers.size(); i++) {
- mService.mAmInternal.finishUserSwitch(startingUsers.get(i));
+ UserState userState = startingUsers.get(i);
+ Slogf.i(TAG, "finishing switch of user %d", userState.mHandle.getIdentifier());
+ mService.mAmInternal.finishUserSwitch(userState);
}
}
}
@@ -1437,8 +1454,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mUserLeaving = true;
}
- mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_TO_FRONT,
- 0 /* flags */, task, options != null ? options.getRemoteTransition() : null);
+ task.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_FRONT,
+ 0 /* flags */, task, task /* readyGroupRef */,
+ options != null ? options.getRemoteTransition() : null);
reason = reason + " findTaskToMoveToFront";
boolean reparented = false;
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
@@ -1611,13 +1629,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Prevent recursion.
return;
}
- if (task.isVisible()) {
- mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CLOSE, task);
- } else {
- // Removing a non-visible task doesn't require a transition, but if there is one
- // collecting, this should be a member just in case.
- mService.getTransitionController().collect(task);
- }
+ task.mTransitionController.requestCloseTransitionIfNeeded(task);
task.mInRemoveTask = true;
try {
task.performClearTask(reason);
@@ -1999,12 +2011,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mHandler.obtainMessage(LAUNCH_TASK_BEHIND_COMPLETE, token).sendToTarget();
}
- /** Checks whether the userid is a profile of the current user. */
- boolean isCurrentProfileLocked(int userId) {
- if (userId == mRootWindowContainer.mCurrentUser) return true;
- return mService.mAmInternal.isCurrentProfile(userId);
- }
-
/**
* Processes the activities to be stopped or destroyed. This should be called when the resumed
* activities are idle or drawn.
@@ -2018,7 +2024,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final ActivityRecord s = mStoppingActivities.get(i);
final boolean animating = s.isAnimating(TRANSITION | PARENTS,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
- || mService.getTransitionController().inTransition(s);
+ || s.inTransition();
ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
+ "finishing=%s", s, s.nowVisible, animating, s.finishing);
if (!animating || mService.mShuttingDown) {
@@ -2073,6 +2079,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
void removeHistoryRecords(WindowProcessController app) {
removeHistoryRecords(mStoppingActivities, app, "mStoppingActivities");
removeHistoryRecords(mFinishingActivities, app, "mFinishingActivities");
+ removeHistoryRecords(mNoHistoryActivities, app, "mNoHistoryActivities");
}
private void removeHistoryRecords(ArrayList<ActivityRecord> list, WindowProcessController app,
@@ -2110,6 +2117,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mWaitingActivityLaunched.get(i).dump(pw, prefix + " ");
}
}
+ pw.println(prefix + "mNoHistoryActivities=" + mNoHistoryActivities);
pw.println();
}
@@ -2134,76 +2142,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
static boolean dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list,
String prefix, String label, boolean complete, boolean brief, boolean client,
String dumpPackage, boolean needNL, Runnable header, Task lastTask) {
- String innerPrefix = null;
- String[] args = null;
boolean printed = false;
- for (int i=list.size()-1; i>=0; i--) {
+ for (int i = list.size() - 1; i >= 0; i--) {
final ActivityRecord r = list.get(i);
- if (dumpPackage != null && !dumpPackage.equals(r.packageName)) {
- continue;
- }
- if (innerPrefix == null) {
- innerPrefix = prefix + " ";
- args = new String[0];
- }
- printed = true;
- final boolean full = !brief && (complete || !r.isInHistory());
- if (needNL) {
- pw.println("");
- needNL = false;
- }
- if (header != null) {
- header.run();
- header = null;
- }
- if (lastTask != r.getTask()) {
- lastTask = r.getTask();
- pw.print(prefix);
- pw.print(full ? "* " : " ");
- pw.println(lastTask);
- if (full) {
- lastTask.dump(pw, prefix + " ");
- } else if (complete) {
- // Complete + brief == give a summary. Isn't that obvious?!?
- if (lastTask.intent != null) {
- pw.print(prefix); pw.print(" ");
- pw.println(lastTask.intent.toInsecureString());
- }
- }
- }
- pw.print(prefix); pw.print(full ? " * " : " "); pw.print(label);
- pw.print(" #"); pw.print(i); pw.print(": ");
- pw.println(r);
- if (full) {
- r.dump(pw, innerPrefix, true /* dumpAll */);
- } else if (complete) {
- // Complete + brief == give a summary. Isn't that obvious?!?
- pw.print(innerPrefix); pw.println(r.intent.toInsecureString());
- if (r.app != null) {
- pw.print(innerPrefix); pw.println(r.app);
- }
- }
- if (client && r.attachedToProcess()) {
- // flush anything that is already in the PrintWriter since the thread is going
- // to write to the file descriptor directly
- pw.flush();
- try {
- TransferPipe tp = new TransferPipe();
- try {
- r.app.getThread().dumpActivity(
- tp.getWriteFd(), r.appToken, innerPrefix, args);
- // Short timeout, since blocking here can deadlock with the application.
- tp.go(fd, 2000);
- } finally {
- tp.kill();
- }
- } catch (IOException e) {
- pw.println(innerPrefix + "Failure while dumping the activity: " + e);
- } catch (RemoteException e) {
- pw.println(innerPrefix + "Got a RemoteException while dumping the activity");
- }
- needNL = true;
- }
+ ActivityRecord.dumpActivity(fd, pw, i, r, prefix, label, complete, brief,
+ client, dumpPackage, needNL, header, lastTask);
+ lastTask = r.getTask();
+ header = null;
+ needNL = client && r.attachedToProcess();
}
return printed;
}
@@ -2216,6 +2162,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final void scheduleIdle() {
if (!mHandler.hasMessages(IDLE_NOW_MSG)) {
+ if (DEBUG_IDLE) Slog.d(TAG_IDLE, "scheduleIdle: Callers=" + Debug.getCallers(4));
mHandler.sendEmptyMessage(IDLE_NOW_MSG);
}
}
@@ -2230,7 +2177,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
void updateTopResumedActivityIfNeeded() {
final ActivityRecord prevTopActivity = mTopResumedActivity;
final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
- if (topRootTask == null || topRootTask.getResumedActivity() == prevTopActivity) {
+ if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
if (mService.isSleepingLocked()) {
// There won't be a next resumed activity. The top process should still be updated
// according to the current top focused activity.
@@ -2252,7 +2199,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
// Update the current top activity.
- mTopResumedActivity = topRootTask.getResumedActivity();
+ mTopResumedActivity = topRootTask.getTopResumedActivity();
+ // Update process state if there is no activity state change (e.g. focus change between
+ // multi-window mode activities) to make sure that the current top has top oom-adj.
+ // If the previous top is null, there should be activity state change from it, Then the
+ // process state should also have been updated so no need to update again.
+ if (mTopResumedActivity != null && prevTopActivity != null) {
+ if (mTopResumedActivity.app != null) {
+ mTopResumedActivity.app.addToPendingTop();
+ }
+ mService.updateOomAdj();
+ }
scheduleTopResumedActivityStateIfNeeded();
mService.updateTopApp(mTopResumedActivity);
@@ -2379,7 +2336,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
- if (mService.getTransitionController().getTransitionPlayer() != null) return;
+ if (task.mTransitionController.isShellTransitionsEnabled()) return;
// Dismiss docked root task. If task appeared to be in docked root task but is not
// resizable - we need to move it to top of fullscreen root task, otherwise it will
// be covered.
@@ -2492,6 +2449,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
return mVisibilityTransactionDepth > 0;
}
+ void setDeferRootVisibilityUpdate(boolean deferUpdate) {
+ mDeferRootVisibilityUpdate = deferUpdate;
+ }
+
+ boolean isRootVisibilityUpdateDeferred() {
+ return mDeferRootVisibilityUpdate;
+ }
+
/**
* Called when the state or visibility of an attached activity is changed.
*
@@ -2559,8 +2524,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
String processName = null;
int uid = 0;
synchronized (mService.mGlobalLock) {
- if (r.attachedToProcess()
- && r.isState(Task.ActivityState.RESTARTING_PROCESS)) {
+ if (r.attachedToProcess() && r.isState(RESTARTING_PROCESS)) {
processName = r.app.mName;
uid = r.app.mUid;
}
@@ -2660,102 +2624,127 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
+ /**
+ * Start the given task from the recent tasks. Do not hold WM global lock when calling this
+ * method to avoid potential deadlock or permission deny by UriGrantsManager when resolving
+ * activity (see {@link ActivityStarter.Request#resolveActivity} and
+ * {@link com.android.server.am.ContentProviderHelper#checkContentProviderUriPermission}).
+ *
+ * @return The result code of starter.
+ */
int startActivityFromRecents(int callingPid, int callingUid, int taskId,
SafeActivityOptions options) {
- Task task = null;
+ final Task task;
+ final int taskCallingUid;
final String callingPackage;
final String callingFeatureId;
final Intent intent;
final int userId;
- int activityType = ACTIVITY_TYPE_UNDEFINED;
- int windowingMode = WINDOWING_MODE_UNDEFINED;
final ActivityOptions activityOptions = options != null
? options.getOptions(this)
: null;
boolean moveHomeTaskForward = true;
- if (activityOptions != null) {
- activityType = activityOptions.getLaunchActivityType();
- windowingMode = activityOptions.getLaunchWindowingMode();
- if (activityOptions.freezeRecentTasksReordering()
- && mRecentTasks.isCallerRecents(callingUid)) {
- mRecentTasks.setFreezeTaskListReordering();
+ synchronized (mService.mGlobalLock) {
+ int activityType = ACTIVITY_TYPE_UNDEFINED;
+ if (activityOptions != null) {
+ activityType = activityOptions.getLaunchActivityType();
+ final int windowingMode = activityOptions.getLaunchWindowingMode();
+ if (activityOptions.freezeRecentTasksReordering()
+ && mService.checkPermission(MANAGE_ACTIVITY_TASKS, callingPid, callingUid)
+ == PERMISSION_GRANTED) {
+ mRecentTasks.setFreezeTaskListReordering();
+ }
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ || activityOptions.getLaunchRootTask() != null) {
+ // Don't move home activity forward if we are launching into primary split or
+ // there is a launch root set.
+ moveHomeTaskForward = false;
+ }
}
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || activityOptions.getLaunchRootTask() != null) {
- // Don't move home activity forward if we are launching into primary split or there
- // is a launch root set.
- moveHomeTaskForward = false;
+ if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
+ throw new IllegalArgumentException("startActivityFromRecents: Task "
+ + taskId + " can't be launch in the home/recents root task.");
}
- }
- if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
- throw new IllegalArgumentException("startActivityFromRecents: Task "
- + taskId + " can't be launch in the home/recents root task.");
- }
- mService.deferWindowLayout();
- try {
- task = mRootWindowContainer.anyTaskForId(taskId,
- MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
- if (task == null) {
- mWindowManager.executeAppTransition();
- throw new IllegalArgumentException(
- "startActivityFromRecents: Task " + taskId + " not found.");
- }
-
- if (moveHomeTaskForward) {
- // We always want to return to the home activity instead of the recents activity
- // from whatever is started from the recents activity, so move the home root task
- // forward.
- // TODO (b/115289124): Multi-display supports for recents.
- mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeRootTaskToFront(
- "startActivityFromRecents");
- }
-
- // If the user must confirm credentials (e.g. when first launching a work app and the
- // Work Challenge is present) let startActivityInPackage handle the intercepting.
- if (!mService.mAmInternal.shouldConfirmCredentials(task.mUserId)
- && task.getRootActivity() != null) {
- final ActivityRecord targetActivity = task.getTopNonFinishingActivity();
-
- mRootWindowContainer.startPowerModeLaunchIfNeeded(
- true /* forceSend */, targetActivity);
- final LaunchingState launchingState =
- mActivityMetricsLogger.notifyActivityLaunching(task.intent);
- try {
- mService.moveTaskToFrontLocked(null /* appThread */, null /* callingPackage */,
- task.mTaskId, 0, options);
- // Apply options to prevent pendingOptions be taken when scheduling activity
- // lifecycle transaction to make sure the override pending app transition will
- // be applied immediately.
- targetActivity.applyOptionsAnimation();
- } finally {
- mActivityMetricsLogger.notifyActivityLaunched(launchingState,
- START_TASK_TO_FRONT, false /* newActivityCreated */, targetActivity,
- activityOptions);
+ boolean shouldStartActivity = false;
+ mService.deferWindowLayout();
+ try {
+ task = mRootWindowContainer.anyTaskForId(taskId,
+ MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
+ if (task == null) {
+ mWindowManager.executeAppTransition();
+ throw new IllegalArgumentException(
+ "startActivityFromRecents: Task " + taskId + " not found.");
}
- mService.getActivityStartController().postStartActivityProcessingForLastStarter(
- task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
- task.getRootTask());
+ if (moveHomeTaskForward) {
+ // We always want to return to the home activity instead of the recents
+ // activity from whatever is started from the recents activity, so move
+ // the home root task forward.
+ // TODO (b/115289124): Multi-display supports for recents.
+ mRootWindowContainer.getDefaultTaskDisplayArea().moveHomeRootTaskToFront(
+ "startActivityFromRecents");
+ }
- // As it doesn't go to ActivityStarter.executeRequest() path, we need to resume
- // app switching here also.
- mService.resumeAppSwitches();
+ // If the user must confirm credentials (e.g. when first launching a work
+ // app and the Work Challenge is present) let startActivityInPackage handle
+ // the intercepting.
+ if (!mService.mAmInternal.shouldConfirmCredentials(task.mUserId)
+ && task.getRootActivity() != null) {
+ final ActivityRecord targetActivity = task.getTopNonFinishingActivity();
+
+ mRootWindowContainer.startPowerModeLaunchIfNeeded(
+ true /* forceSend */, targetActivity);
+ final LaunchingState launchingState =
+ mActivityMetricsLogger.notifyActivityLaunching(task.intent);
+ try {
+ mService.moveTaskToFrontLocked(null /* appThread */,
+ null /* callingPackage */, task.mTaskId, 0, options);
+ // Apply options to prevent pendingOptions be taken when scheduling
+ // activity lifecycle transaction to make sure the override pending app
+ // transition will be applied immediately.
+ targetActivity.applyOptionsAnimation();
+ } finally {
+ mActivityMetricsLogger.notifyActivityLaunched(launchingState,
+ START_TASK_TO_FRONT, false /* newActivityCreated */,
+ targetActivity, activityOptions);
+ }
- return ActivityManager.START_TASK_TO_FRONT;
+ mService.getActivityStartController().postStartActivityProcessingForLastStarter(
+ task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
+ task.getRootTask());
+
+ // As it doesn't go to ActivityStarter.executeRequest() path, we need to resume
+ // app switching here also.
+ mService.resumeAppSwitches();
+ return ActivityManager.START_TASK_TO_FRONT;
+ }
+ // The task is empty or needs to show the confirmation for credential.
+ shouldStartActivity = true;
+ } finally {
+ if (!shouldStartActivity) {
+ mService.continueWindowLayout();
+ }
}
+ taskCallingUid = task.mCallingUid;
callingPackage = task.mCallingPackage;
callingFeatureId = task.mCallingFeatureId;
intent = task.intent;
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
userId = task.mUserId;
- return mService.getActivityStartController().startActivityInPackage(task.mCallingUid,
+ }
+ // ActivityStarter will acquire the lock where the places need, so execute the request
+ // outside of the lock.
+ try {
+ return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
null, 0, 0, options, userId, task, "startActivityFromRecents",
false /* validateIncomingUser */, null /* originatingPendingIntent */,
false /* allowBackgroundActivityStart */);
} finally {
- mService.continueWindowLayout();
+ synchronized (mService.mGlobalLock) {
+ mService.continueWindowLayout();
+ }
}
}
@@ -2778,7 +2767,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
boolean matches(ActivityRecord r) {
- return mTargetComponent.equals(r.mActivityComponent) || mLaunchingState.contains(r);
+ if (!mLaunchingState.hasActiveTransitionInfo()) {
+ return mTargetComponent.equals(r.mActivityComponent);
+ }
+ return mLaunchingState.contains(r);
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 529c4f608743..5899a4e89ca7 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import android.annotation.NonNull;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -52,7 +53,7 @@ interface AnimationAdapter {
* @param finishCallback The callback to be invoked when the animation has finished.
*/
void startAnimation(SurfaceControl animationLeash, Transaction t, @AnimationType int type,
- OnAnimationFinishedCallback finishCallback);
+ @NonNull OnAnimationFinishedCallback finishCallback);
/**
* Called when the animation that was started with {@link #startAnimation} was cancelled by the
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 892db9c33dbd..c881864dff25 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -31,7 +31,6 @@ import android.util.SparseArray;
import android.view.InputApplicationHandle;
import com.android.server.am.ActivityManagerService;
-import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
import java.io.File;
import java.util.ArrayList;
@@ -81,21 +80,19 @@ class AnrController {
final boolean aboveSystem;
final ActivityRecord activity;
synchronized (mService.mGlobalLock) {
- WindowState windowState = mService.mInputToWindowMap.get(inputToken);
- if (windowState != null) {
- pid = windowState.mSession.mPid;
- activity = windowState.mActivityRecord;
- Slog.i(TAG_WM, "ANR in " + windowState.mAttrs.getTitle() + ". Reason:" + reason);
- } else {
- EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken);
- if (embeddedWindow == null) {
- Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
- return;
- }
- pid = embeddedWindow.mOwnerPid;
- windowState = embeddedWindow.mHostWindowState;
- activity = null; // Don't blame the host process, instead blame the embedded pid.
+ InputTarget target = mService.getInputTargetFromToken(inputToken);
+ if (target == null) {
+ Slog.e(TAG_WM, "Unknown token, dropping notifyConnectionUnresponsive request");
+ return;
}
+
+ WindowState windowState = target.getWindowState();
+ pid = target.getPid();
+ // Blame the activity if the input token belongs to the window. If the target is
+ // embedded, then we will blame the pid instead.
+ activity = (windowState.mInputChannelToken == inputToken)
+ ? windowState.mActivityRecord : null;
+ Slog.i(TAG_WM, "ANR in " + target + ". Reason:" + reason);
aboveSystem = isWindowAboveSystem(windowState);
dumpAnrStateLocked(activity, windowState, reason);
}
@@ -109,19 +106,12 @@ class AnrController {
void notifyWindowResponsive(IBinder inputToken) {
final int pid;
synchronized (mService.mGlobalLock) {
- WindowState windowState = mService.mInputToWindowMap.get(inputToken);
- if (windowState != null) {
- pid = windowState.mSession.mPid;
- } else {
- // Check if the token belongs to an embedded window.
- EmbeddedWindow embeddedWindow = mService.mEmbeddedWindowController.get(inputToken);
- if (embeddedWindow == null) {
- Slog.e(TAG_WM,
- "Unknown token, dropping notifyWindowConnectionResponsive request");
- return;
- }
- pid = embeddedWindow.mOwnerPid;
+ InputTarget target = mService.getInputTargetFromToken(inputToken);
+ if (target == null) {
+ Slog.e(TAG_WM, "Unknown token, dropping notifyWindowConnectionResponsive request");
+ return;
}
+ pid = target.getPid();
}
mService.mAmInternal.inputDispatchingResumed(pid);
}
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 7f0adcacc951..558939611905 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -35,10 +35,10 @@ import android.os.UserHandle;
*/
class AppTaskImpl extends IAppTask.Stub {
private static final String TAG = "AppTaskImpl";
- private ActivityTaskManagerService mService;
+ private final ActivityTaskManagerService mService;
- private int mTaskId;
- private int mCallingUid;
+ private final int mTaskId;
+ private final int mCallingUid;
public AppTaskImpl(ActivityTaskManagerService service, int taskId, int callingUid) {
mService = service;
@@ -113,9 +113,9 @@ class AppTaskImpl extends IAppTask.Stub {
return;
}
}
- mService.mTaskSupervisor.startActivityFromRecents(callingPid,
- callingUid, mTaskId, null);
}
+ mService.mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId,
+ null /* options */);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index c61cfeeac917..421a1c916fdc 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -40,6 +40,9 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
@@ -78,10 +81,7 @@ 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.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
-import static com.android.internal.policy.TransitionAnimation.THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
+import static com.android.internal.policy.TransitionAnimation.prepareThumbnailAnimationWithDuration;
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.server.wm.AppTransitionProto.APP_TRANSITION_STATE;
@@ -102,12 +102,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Picture;
import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.os.Binder;
import android.os.Debug;
@@ -132,7 +127,6 @@ import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
-import android.view.animation.ClipRectAnimation;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.ScaleAnimation;
@@ -222,6 +216,7 @@ public class AppTransition implements Dump {
private int mNextAppTransitionEnter;
private int mNextAppTransitionExit;
private int mNextAppTransitionInPlace;
+ private boolean mNextAppTransitionIsSync;
// Keyed by WindowContainer hashCode.
private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
@@ -357,6 +352,13 @@ public class AppTransition implements Dump {
fetchAppTransitionSpecsFromFuture();
}
+ void abort() {
+ if (mRemoteAnimationController != null) {
+ mRemoteAnimationController.cancelAnimation("aborted");
+ }
+ clear();
+ }
+
boolean isRunning() {
return mAppTransitionState == APP_STATE_RUNNING;
}
@@ -445,6 +447,7 @@ public class AppTransition implements Dump {
int redoLayout = notifyAppTransitionStartingLocked(
AppTransition.isKeyguardGoingAwayTransitOld(transit),
+ AppTransition.isKeyguardOccludeTransitOld(transit),
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
@@ -479,6 +482,8 @@ public class AppTransition implements Dump {
mNextAppTransitionAnimationsSpecsFuture = null;
mDefaultNextAppTransitionAnimationSpec = null;
mAnimationFinishedCallback = null;
+ mOverrideTaskTransition = false;
+ mNextAppTransitionIsSync = false;
}
void freeze() {
@@ -557,12 +562,14 @@ public class AppTransition implements Dump {
}
}
- private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOcclude, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
- duration, statusBarAnimationStartTime, statusBarAnimationDuration);
+ keyguardOcclude, duration, statusBarAnimationStartTime,
+ statusBarAnimationDuration);
}
return redoLayout;
}
@@ -645,24 +652,6 @@ public class AppTransition implements Dump {
/**
* Prepares the specified animation with a standard duration, interpolator, etc.
*/
- Animation prepareThumbnailAnimationWithDuration(@Nullable Animation a, int appWidth,
- int appHeight, long duration, Interpolator interpolator) {
- if (a != null) {
- if (duration > 0) {
- a.setDuration(duration);
- }
- a.setFillAfter(true);
- if (interpolator != null) {
- a.setInterpolator(interpolator);
- }
- a.initialize(appWidth, appHeight, appWidth, appHeight);
- }
- return a;
- }
-
- /**
- * Prepares the specified animation with a standard duration, interpolator, etc.
- */
Animation prepareThumbnailAnimation(Animation a, int appWidth, int appHeight, int transit) {
// Pick the desired duration. If this is an inter-activity transition,
// it is the standard duration for that. Otherwise we use the longer
@@ -682,56 +671,16 @@ public class AppTransition implements Dump {
}
/**
- * Return the current thumbnail transition state.
- */
- int getThumbnailTransitionState(boolean enter) {
- if (enter) {
- if (mNextAppTransitionScaleUp) {
- return THUMBNAIL_TRANSITION_ENTER_SCALE_UP;
- } else {
- return THUMBNAIL_TRANSITION_ENTER_SCALE_DOWN;
- }
- } else {
- if (mNextAppTransitionScaleUp) {
- return THUMBNAIL_TRANSITION_EXIT_SCALE_UP;
- } else {
- return THUMBNAIL_TRANSITION_EXIT_SCALE_DOWN;
- }
- }
- }
-
- /**
* Creates an overlay with a background color and a thumbnail for the cross profile apps
* animation.
*/
HardwareBuffer createCrossProfileAppsThumbnail(
@DrawableRes int thumbnailDrawableRes, Rect frame) {
- final int width = frame.width();
- final int height = frame.height();
-
- final Picture picture = new Picture();
- final Canvas canvas = picture.beginRecording(width, height);
- canvas.drawColor(Color.argb(0.6f, 0, 0, 0));
- final int thumbnailSize = mService.mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.cross_profile_apps_thumbnail_size);
- final Drawable drawable = mService.mContext.getDrawable(thumbnailDrawableRes);
- drawable.setBounds(
- (width - thumbnailSize) / 2,
- (height - thumbnailSize) / 2,
- (width + thumbnailSize) / 2,
- (height + thumbnailSize) / 2);
- drawable.setTint(mContext.getColor(android.R.color.white));
- drawable.draw(canvas);
- picture.endRecording();
-
- return Bitmap.createBitmap(picture).getHardwareBuffer();
+ return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame);
}
Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) {
- final Animation animation =
- mTransitionAnimation.loadCrossProfileAppThumbnailEnterAnimation();
- return prepareThumbnailAnimationWithDuration(animation, appRect.width(),
- appRect.height(), 0, null);
+ return mTransitionAnimation.createCrossProfileAppsThumbnailAnimationLocked(appRect);
}
/**
@@ -739,115 +688,14 @@ public class AppTransition implements Dump {
* when a thumbnail is specified with the pending animation override.
*/
Animation createThumbnailAspectScaleAnimationLocked(Rect appRect, @Nullable Rect contentInsets,
- HardwareBuffer thumbnailHeader, WindowContainer container, int uiMode,
- int orientation) {
- Animation a;
- final int thumbWidthI = thumbnailHeader.getWidth();
- final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
- final int thumbHeightI = thumbnailHeader.getHeight();
- final int appWidth = appRect.width();
-
- float scaleW = appWidth / thumbWidth;
- getNextAppTransitionStartRect(container, mTmpRect);
- final float fromX;
- float fromY;
- final float toX;
- float toY;
- final float pivotX;
- final float pivotY;
- if (shouldScaleDownThumbnailTransition(uiMode, orientation)) {
- fromX = mTmpRect.left;
- fromY = mTmpRect.top;
-
- // For the curved translate animation to work, the pivot points needs to be at the
- // same absolute position as the one from the real surface.
- toX = mTmpRect.width() / 2 * (scaleW - 1f) + appRect.left;
- toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top;
- pivotX = mTmpRect.width() / 2;
- pivotY = appRect.height() / 2 / scaleW;
- if (mGridLayoutRecentsEnabled) {
- // In the grid layout, the header is displayed above the thumbnail instead of
- // overlapping it.
- fromY -= thumbHeightI;
- toY -= thumbHeightI * scaleW;
- }
- } else {
- pivotX = 0;
- pivotY = 0;
- fromX = mTmpRect.left;
- fromY = mTmpRect.top;
- toX = appRect.left;
- toY = appRect.top;
- }
- final long duration = getAspectScaleDuration();
- final Interpolator interpolator = getAspectScaleInterpolator();
- if (mNextAppTransitionScaleUp) {
- // Animation up from the thumbnail to the full screen
- Animation scale = new ScaleAnimation(1f, scaleW, 1f, scaleW, pivotX, pivotY);
- scale.setInterpolator(interpolator);
- scale.setDuration(duration);
- Animation alpha = new AlphaAnimation(1f, 0f);
- alpha.setInterpolator(mThumbnailFadeOutInterpolator);
- alpha.setDuration(duration);
- Animation translate = createCurvedMotion(fromX, toX, fromY, toY);
- translate.setInterpolator(interpolator);
- translate.setDuration(duration);
-
- mTmpFromClipRect.set(0, 0, thumbWidthI, thumbHeightI);
- mTmpToClipRect.set(appRect);
-
- // Containing frame is in screen space, but we need the clip rect in the
- // app space.
- mTmpToClipRect.offsetTo(0, 0);
- mTmpToClipRect.right = (int) (mTmpToClipRect.right / scaleW);
- mTmpToClipRect.bottom = (int) (mTmpToClipRect.bottom / scaleW);
-
- if (contentInsets != null) {
- mTmpToClipRect.inset((int) (-contentInsets.left * scaleW),
- (int) (-contentInsets.top * scaleW),
- (int) (-contentInsets.right * scaleW),
- (int) (-contentInsets.bottom * scaleW));
- }
-
- Animation clipAnim = new ClipRectAnimation(mTmpFromClipRect, mTmpToClipRect);
- clipAnim.setInterpolator(interpolator);
- clipAnim.setDuration(duration);
-
- // This AnimationSet uses the Interpolators assigned above.
- AnimationSet set = new AnimationSet(false);
- set.addAnimation(scale);
- if (!mGridLayoutRecentsEnabled) {
- // In the grid layout, the header should be shown for the whole animation.
- set.addAnimation(alpha);
- }
- set.addAnimation(translate);
- set.addAnimation(clipAnim);
- a = set;
- } else {
- // Animation down from the full screen to the thumbnail
- Animation scale = new ScaleAnimation(scaleW, 1f, scaleW, 1f, pivotX, pivotY);
- scale.setInterpolator(interpolator);
- scale.setDuration(duration);
- Animation alpha = new AlphaAnimation(0f, 1f);
- alpha.setInterpolator(mThumbnailFadeInInterpolator);
- alpha.setDuration(duration);
- Animation translate = createCurvedMotion(toX, fromX, toY, fromY);
- translate.setInterpolator(interpolator);
- translate.setDuration(duration);
-
- // This AnimationSet uses the Interpolators assigned above.
- AnimationSet set = new AnimationSet(false);
- set.addAnimation(scale);
- if (!mGridLayoutRecentsEnabled) {
- // In the grid layout, the header should be shown for the whole animation.
- set.addAnimation(alpha);
- }
- set.addAnimation(translate);
- a = set;
-
- }
- return prepareThumbnailAnimationWithDuration(a, appWidth, appRect.height(), 0,
- null);
+ HardwareBuffer thumbnailHeader, WindowContainer container, int orientation) {
+ AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
+ container.hashCode());
+ return mTransitionAnimation.createThumbnailAspectScaleAnimationLocked(appRect,
+ contentInsets, thumbnailHeader, orientation, spec != null ? spec.rect : null,
+ mDefaultNextAppTransitionAnimationSpec != null
+ ? mDefaultNextAppTransitionAnimationSpec.rect : null,
+ mNextAppTransitionScaleUp);
}
private Animation createCurvedMotion(float fromX, float toX, float fromY, float toY) {
@@ -1047,7 +895,7 @@ public class AppTransition implements Dump {
+ "transit=%s Callers=%s",
a, appTransitionOldToString(transit), Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
- a = mTransitionAnimation.createClipRevealAnimationLocked(
+ a = mTransitionAnimation.createClipRevealAnimationLockedCompat(
transit, enter, frame, displayFrame,
mDefaultNextAppTransitionAnimationSpec != null
? mDefaultNextAppTransitionAnimationSpec.rect : null);
@@ -1056,7 +904,7 @@ public class AppTransition implements Dump {
+ "transit=%s Callers=%s",
a, appTransitionOldToString(transit), Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
- a = mTransitionAnimation.createScaleUpAnimationLocked(transit, enter, frame,
+ a = mTransitionAnimation.createScaleUpAnimationLockedCompat(transit, enter, frame,
mDefaultNextAppTransitionAnimationSpec != null
? mDefaultNextAppTransitionAnimationSpec.rect : null);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1068,8 +916,8 @@ public class AppTransition implements Dump {
mNextAppTransitionScaleUp =
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
final HardwareBuffer thumbnailHeader = getAppTransitionThumbnailHeader(container);
- a = mTransitionAnimation.createThumbnailEnterExitAnimationLocked(
- getThumbnailTransitionState(enter), frame, transit, thumbnailHeader,
+ a = mTransitionAnimation.createThumbnailEnterExitAnimationLockedCompat(enter,
+ mNextAppTransitionScaleUp, frame, transit, thumbnailHeader,
mDefaultNextAppTransitionAnimationSpec != null
? mDefaultNextAppTransitionAnimationSpec.rect : null);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1084,9 +932,9 @@ public class AppTransition implements Dump {
(mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
container.hashCode());
- a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(
- getThumbnailTransitionState(enter), orientation, transit, frame,
- insets, surfaceInsets, stableInsets, freeform, spec != null ? spec.rect : null,
+ a = mTransitionAnimation.createAspectScaledThumbnailEnterExitAnimationLocked(enter,
+ mNextAppTransitionScaleUp, orientation, transit, frame, insets, surfaceInsets,
+ stableInsets, freeform, spec != null ? spec.rect : null,
mDefaultNextAppTransitionAnimationSpec != null
? mDefaultNextAppTransitionAnimationSpec.rect : null);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1102,7 +950,7 @@ public class AppTransition implements Dump {
"applyAnimation NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS: "
+ "anim=%s transit=%s isEntrance=true Callers=%s",
a, appTransitionOldToString(transit), Debug.getCallers(3));
- } else if (transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE) {
+ } else if (isChangeTransitOld(transit)) {
// In the absence of a specific adapter, we just want to keep everything stationary.
a = new AlphaAnimation(1.f, 1.f);
a.setDuration(WindowChangeAnimationSpec.ANIMATION_DURATION);
@@ -1168,6 +1016,21 @@ public class AppTransition implements Dump {
animAttr = enter
? WindowAnimation_launchTaskBehindSourceAnimation
: WindowAnimation_launchTaskBehindTargetAnimation;
+ break;
+ // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
+ // need new TaskFragment transition.
+ case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+ animAttr = enter
+ ? WindowAnimation_activityOpenEnterAnimation
+ : WindowAnimation_activityOpenExitAnimation;
+ break;
+ // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
+ // need new TaskFragment transition.
+ case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_activityCloseEnterAnimation
+ : WindowAnimation_activityCloseExitAnimation;
+ break;
}
a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
@@ -1318,13 +1181,19 @@ public class AppTransition implements Dump {
}
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
+ overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */);
+ }
+
+ void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
+ boolean sync) {
ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
isTransitionSet(), remoteAnimationAdapter);
- if (isTransitionSet()) {
+ if (isTransitionSet() && !mNextAppTransitionIsSync) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler);
+ mNextAppTransitionIsSync = sync;
}
}
@@ -1472,6 +1341,15 @@ public class AppTransition implements Dump {
case TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE: {
return "TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE";
}
+ case TRANSIT_OLD_TASK_FRAGMENT_OPEN: {
+ return "TRANSIT_OLD_TASK_FRAGMENT_OPEN";
+ }
+ case TRANSIT_OLD_TASK_FRAGMENT_CLOSE: {
+ return "TRANSIT_OLD_TASK_FRAGMENT_CLOSE";
+ }
+ case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: {
+ return "TRANSIT_OLD_TASK_FRAGMENT_CHANGE";
+ }
default: {
return "<UNKNOWN: " + transition + ">";
}
@@ -1676,7 +1554,7 @@ public class AppTransition implements Dump {
}
boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
- if (mService.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+ if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
return false;
}
mNextAppTransitionRequests.add(transit);
@@ -1729,7 +1607,8 @@ public class AppTransition implements Dump {
}
static boolean isChangeTransitOld(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+ return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
}
static boolean isClosingTransitOld(@TransitionOldType int transit) {
@@ -1755,21 +1634,21 @@ public class AppTransition implements Dump {
}
@TransitionType int getKeyguardTransition() {
+ if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) {
+ return TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+ final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
+ final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE);
+ // No keyguard related transition requests.
+ if (unoccludeIndex == -1 && occludeIndex == -1) {
+ return TRANSIT_NONE;
+ }
// In case we unocclude Keyguard and occlude it again, meaning that we never actually
// unoccclude/occlude Keyguard, but just run a normal transition.
- final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
- if (occludeIndex != -1
- && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) {
+ if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) {
return TRANSIT_NONE;
}
-
- for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) {
- final @TransitionType int transit = mNextAppTransitionRequests.get(i);
- if (isKeyguardTransit(transit)) {
- return transit;
- }
- }
- return TRANSIT_NONE;
+ return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE;
}
@TransitionType int getFirstAppTransition() {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c869ec67776d..721907c21904 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -39,6 +39,9 @@ import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
@@ -62,12 +65,16 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_S
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
import static com.android.server.wm.AppTransition.isNormalTransit;
+import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldAttachNavBarToApp;
+import static com.android.server.wm.NonAppWindowAnimationAdapter.shouldStartNonAppWindowAnimationsForKeyguardExit;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.WallpaperAnimationAdapter.shouldStartWallpaperAnimation;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Trace;
import android.util.ArrayMap;
@@ -82,10 +89,13 @@ import android.view.WindowManager.TransitionFlags;
import android.view.WindowManager.TransitionOldType;
import android.view.WindowManager.TransitionType;
import android.view.animation.Animation;
+import android.window.ITaskFragmentOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.function.Predicate;
@@ -102,7 +112,22 @@ public class AppTransitionController {
private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
private static final int KEYGUARD_GOING_AWAY_ANIMATION_DURATION = 400;
+ private static final int TYPE_NONE = 0;
+ private static final int TYPE_ACTIVITY = 1;
+ private static final int TYPE_TASK_FRAGMENT = 2;
+ private static final int TYPE_TASK = 3;
+
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_NONE,
+ TYPE_ACTIVITY,
+ TYPE_TASK_FRAGMENT,
+ TYPE_TASK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface TransitContainerType {}
+
private final ArrayMap<WindowContainer, Integer> mTempTransitionReasons = new ArrayMap<>();
+ private final ArrayList<WindowContainer> mTempTransitionWindows = new ArrayList<>();
AppTransitionController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
@@ -144,13 +169,16 @@ public class AppTransitionController {
void handleAppTransitionReady() {
mTempTransitionReasons.clear();
if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
- || !transitionGoodToGo(mDisplayContent.mChangingContainers,
- mTempTransitionReasons)) {
+ || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)
+ || !transitionGoodToGoForTaskFragments()) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
+ // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause.
+ mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow,
+ true /* traverseTopToBottom */);
// TODO(new-app-transition): Remove code using appTransition.getAppTransition()
final AppTransition appTransition = mDisplayContent.mAppTransition;
@@ -185,8 +213,8 @@ public class AppTransitionController {
mDisplayContent.mOpeningApps);
final @TransitionOldType int transit = getTransitCompatType(
- mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,
+ mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,
mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),
mDisplayContent.mSkipAppTransitionAnimation);
mDisplayContent.mSkipAppTransitionAnimation = false;
@@ -213,7 +241,13 @@ public class AppTransitionController {
final ActivityRecord topChangingApp =
getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
- overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+
+ // Check if there is any override
+ if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
+ // Unfreeze the windows that were previously frozen for TaskFragment animation.
+ unfreezeEmbeddedChangingWindows();
+ overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+ }
final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
|| containsVoiceInteraction(mDisplayContent.mOpeningApps);
@@ -267,6 +301,7 @@ public class AppTransitionController {
* @param appTransition {@link AppTransition} for managing app transition state.
* @param openingApps {@link ActivityRecord}s which are becoming visible.
* @param closingApps {@link ActivityRecord}s which are becoming invisible.
+ * @param changingContainers {@link WindowContainer}s which are changed in configuration.
* @param wallpaperTarget If non-null, this is the currently visible window that is associated
* with the wallpaper.
* @param oldWallpaper The currently visible window that is associated with the wallpaper in
@@ -275,8 +310,8 @@ public class AppTransitionController {
*/
static @TransitionOldType int getTransitCompatType(AppTransition appTransition,
ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps,
- @Nullable WindowState wallpaperTarget, @Nullable WindowState oldWallpaper,
- boolean skipAppTransitionAnimation) {
+ ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
+ @Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
// Determine if closing and opening app token sets are wallpaper targets, in which case
// special animations are needed.
@@ -309,8 +344,18 @@ public class AppTransitionController {
// Special transitions
// TODO(new-app-transitions): Revisit if those can be rewritten by using flags.
- if (appTransition.containsTransitRequest(TRANSIT_CHANGE)) {
- return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+ if (appTransition.containsTransitRequest(TRANSIT_CHANGE) && !changingContainers.isEmpty()) {
+ @TransitContainerType int changingType =
+ getTransitContainerType(changingContainers.valueAt(0));
+ switch (changingType) {
+ case TYPE_TASK:
+ return TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+ case TYPE_TASK_FRAGMENT:
+ return TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+ default:
+ throw new IllegalStateException(
+ "TRANSIT_CHANGE with unrecognized changing type=" + changingType);
+ }
}
if ((flags & TRANSIT_FLAG_APP_CRASHED) != 0) {
return TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
@@ -387,33 +432,38 @@ public class AppTransitionController {
openingApps, closingApps, true /* visible */);
final ArraySet<WindowContainer> closingWcs = getAnimationTargets(
openingApps, closingApps, false /* visible */);
- final boolean isActivityOpening = !openingWcs.isEmpty()
- && openingWcs.valueAt(0).asActivityRecord() != null;
- final boolean isActivityClosing = !closingWcs.isEmpty()
- && closingWcs.valueAt(0).asActivityRecord() != null;
- final boolean isTaskOpening = !openingWcs.isEmpty() && !isActivityOpening;
- final boolean isTaskClosing = !closingWcs.isEmpty() && !isActivityClosing;
-
- if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && isTaskOpening) {
+ final WindowContainer<?> openingContainer = !openingWcs.isEmpty()
+ ? openingWcs.valueAt(0) : null;
+ final WindowContainer<?> closingContainer = !closingWcs.isEmpty()
+ ? closingWcs.valueAt(0) : null;
+ @TransitContainerType int openingType = getTransitContainerType(openingContainer);
+ @TransitContainerType int closingType = getTransitContainerType(closingContainer);
+ if (appTransition.containsTransitRequest(TRANSIT_TO_FRONT) && openingType == TYPE_TASK) {
return TRANSIT_OLD_TASK_TO_FRONT;
}
- if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && isTaskClosing) {
+ if (appTransition.containsTransitRequest(TRANSIT_TO_BACK) && closingType == TYPE_TASK) {
return TRANSIT_OLD_TASK_TO_BACK;
}
if (appTransition.containsTransitRequest(TRANSIT_OPEN)) {
- if (isTaskOpening) {
+ if (openingType == TYPE_TASK) {
return (appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0
? TRANSIT_OLD_TASK_OPEN_BEHIND : TRANSIT_OLD_TASK_OPEN;
}
- if (isActivityOpening) {
+ if (openingType == TYPE_ACTIVITY) {
return TRANSIT_OLD_ACTIVITY_OPEN;
}
+ if (openingType == TYPE_TASK_FRAGMENT) {
+ return TRANSIT_OLD_TASK_FRAGMENT_OPEN;
+ }
}
if (appTransition.containsTransitRequest(TRANSIT_CLOSE)) {
- if (isTaskClosing) {
+ if (closingType == TYPE_TASK) {
return TRANSIT_OLD_TASK_CLOSE;
}
- if (isActivityClosing) {
+ if (closingType == TYPE_TASK_FRAGMENT) {
+ return TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+ }
+ if (closingType == TYPE_ACTIVITY) {
for (int i = closingApps.size() - 1; i >= 0; i--) {
if (closingApps.valueAt(i).visibleIgnoringKeyguard) {
return TRANSIT_OLD_ACTIVITY_CLOSE;
@@ -430,6 +480,24 @@ public class AppTransitionController {
return TRANSIT_OLD_NONE;
}
+ @TransitContainerType
+ private static int getTransitContainerType(@Nullable WindowContainer<?> container) {
+ if (container == null) {
+ return TYPE_NONE;
+ }
+ if (container.asTask() != null) {
+ return TYPE_TASK;
+ }
+ if (container.asTaskFragment() != null) {
+ return TYPE_TASK_FRAGMENT;
+ }
+ if (container.asActivityRecord() != null) {
+ return TYPE_ACTIVITY;
+ }
+ return TYPE_NONE;
+ }
+
+ @Nullable
private static WindowManager.LayoutParams getAnimLp(ActivityRecord activity) {
final WindowState mainWindow = activity != null ? activity.findMainWindow() : null;
return mainWindow != null ? mainWindow.mAttrs : null;
@@ -452,6 +520,142 @@ public class AppTransitionController {
: null;
}
+ private void unfreezeEmbeddedChangingWindows() {
+ final ArraySet<WindowContainer> changingContainers = mDisplayContent.mChangingContainers;
+ for (int i = changingContainers.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = changingContainers.valueAt(i);
+ if (wc.isEmbedded()) {
+ wc.mSurfaceFreezer.unfreeze(wc.getSyncTransaction());
+ }
+ }
+ }
+
+ private boolean transitionMayContainNonAppWindows(@TransitionOldType int transit) {
+ // We don't want to have the client to animate any non-app windows.
+ // Having {@code transit} of those types doesn't mean it will contain non-app windows, but
+ // non-app windows will only be included with those transition types. And we don't currently
+ // have any use case of those for TaskFragment transition.
+ return shouldStartNonAppWindowAnimationsForKeyguardExit(transit)
+ || shouldAttachNavBarToApp(mService, mDisplayContent, transit)
+ || shouldStartWallpaperAnimation(mDisplayContent);
+ }
+
+ /**
+ * Finds the common {@link android.window.TaskFragmentOrganizer} that organizes all app windows
+ * in the current transition.
+ * @return {@code null} if there is no such organizer, or if there are more than one.
+ */
+ @Nullable
+ private ITaskFragmentOrganizer findTaskFragmentOrganizerForAllWindows() {
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mChangingContainers);
+
+ // It should only animated by the organizer if all windows are below the same leaf Task.
+ Task leafTask = null;
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = getAppFromContainer(mTempTransitionWindows.get(i));
+ if (r == null) {
+ leafTask = null;
+ break;
+ }
+ // The activity may be a child of embedded Task, but we want to find the owner Task.
+ // As a result, find the organized TaskFragment first.
+ final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
+ // There are also cases where the Task contains non-embedded activity, such as launching
+ // split TaskFragments from a non-embedded activity.
+ // The hierarchy may looks like this:
+ // - Task
+ // - Activity
+ // - TaskFragment
+ // - Activity
+ // - TaskFragment
+ // - Activity
+ // We also want to have the organizer handle the transition for such case.
+ final Task task = organizedTaskFragment != null
+ ? organizedTaskFragment.getTask()
+ : r.getTask();
+ if (task == null) {
+ leafTask = null;
+ break;
+ }
+ // We don't want the organizer to handle transition of other non-embedded Task.
+ if (leafTask != null && leafTask != task) {
+ leafTask = null;
+ break;
+ }
+ final ActivityRecord rootActivity = task.getRootActivity();
+ // We don't want the organizer to handle transition when the whole app is closing.
+ if (rootActivity == null) {
+ leafTask = null;
+ break;
+ }
+ // We don't want the organizer to handle transition of non-embedded activity of other
+ // app.
+ if (r.getUid() != task.effectiveUid && !r.isEmbedded()) {
+ leafTask = null;
+ break;
+ }
+ leafTask = task;
+ }
+ mTempTransitionWindows.clear();
+ if (leafTask == null) {
+ return null;
+ }
+
+ // We don't support remote animation for Task with multiple TaskFragmentOrganizers.
+ final ITaskFragmentOrganizer[] organizer = new ITaskFragmentOrganizer[1];
+ final boolean hasMultipleOrganizers = leafTask.forAllLeafTaskFragments(taskFragment -> {
+ final ITaskFragmentOrganizer tfOrganizer = taskFragment.getTaskFragmentOrganizer();
+ if (tfOrganizer == null) {
+ return false;
+ }
+ if (organizer[0] != null && !organizer[0].asBinder().equals(tfOrganizer.asBinder())) {
+ return true;
+ }
+ organizer[0] = tfOrganizer;
+ return false;
+ });
+ if (hasMultipleOrganizers) {
+ ProtoLog.e(WM_DEBUG_APP_TRANSITIONS, "We don't support remote animation for"
+ + " Task with multiple TaskFragmentOrganizers.");
+ return null;
+ }
+ return organizer[0];
+ }
+
+ /**
+ * Overrides the pending transition with the remote animation defined by the
+ * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+ * {@link TaskFragment} that are organized by the same organizer.
+ *
+ * @return {@code true} if the transition is overridden.
+ */
+ private boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+ ArraySet<Integer> activityTypes) {
+ if (transitionMayContainNonAppWindows(transit)) {
+ return false;
+ }
+
+ final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizerForAllWindows();
+ final RemoteAnimationDefinition definition = organizer != null
+ ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
+ .getRemoteAnimationDefinition(organizer)
+ : null;
+ final RemoteAnimationAdapter adapter = definition != null
+ ? definition.getAdapter(transit, activityTypes)
+ : null;
+ if (adapter == null) {
+ return false;
+ }
+ mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Override with TaskFragment remote animation for transit=%s",
+ AppTransition.appTransitionOldToString(transit));
+ return true;
+ }
+
/**
* Overrides the pending transition with the remote animation defined for the transition in the
* set of defined remote animations in the app window token.
@@ -464,19 +668,28 @@ public class AppTransitionController {
}
final RemoteAnimationAdapter adapter =
getRemoteAnimationOverride(animLpActivity, transit, activityTypes);
- if (adapter != null) {
+ if (adapter != null
+ && mDisplayContent.mAppTransition.getRemoteAnimationController() == null) {
mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
}
}
+ @Nullable
+ static Task findRootTaskFromContainer(WindowContainer wc) {
+ return wc.asTaskFragment() != null ? wc.asTaskFragment().getRootTask()
+ : wc.asActivityRecord().getRootTask();
+ }
+
+ @Nullable
static ActivityRecord getAppFromContainer(WindowContainer wc) {
- return wc.asTask() != null ? wc.asTask().getTopNonFinishingActivity()
+ return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
: wc.asActivityRecord();
}
/**
* @return The window token that determines the animation theme.
*/
+ @Nullable
private ActivityRecord findAnimLayoutParamsToken(@TransitionOldType int transit,
ArraySet<Integer> activityTypes) {
ActivityRecord result;
@@ -489,7 +702,7 @@ public class AppTransitionController {
w -> w.getRemoteAnimationDefinition() != null
&& w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
if (result != null) {
- return getAppFromContainer(result);
+ return result;
}
result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
w -> w.fillsParent() && w.findMainWindow() != null);
@@ -632,6 +845,7 @@ public class AppTransitionController {
boolean canPromote = true;
if (parent == null || !parent.canCreateRemoteAnimationTarget()
+ || !parent.canBeAnimationTarget()
// We cannot promote the animation on Task's parent when the task is in
// clearing task in case the animating get stuck when performing the opening
// task that behind it.
@@ -717,6 +931,10 @@ public class AppTransitionController {
voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,
voiceInteraction);
+ final RecentsAnimationController rac = mService.getRecentsAnimationController();
+ if (rac != null) {
+ rac.sendTasksAppeared();
+ }
for (int i = 0; i < openingApps.size(); ++i) {
openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;
@@ -727,7 +945,7 @@ public class AppTransitionController {
final AccessibilityController accessibilityController =
mDisplayContent.mWmService.mAccessibilityController;
- if (accessibilityController != null) {
+ if (accessibilityController.hasCallbacks()) {
accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);
}
}
@@ -840,71 +1058,113 @@ public class AppTransitionController {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Checking %d opening apps (frozen=%b timeout=%b)...", apps.size(),
mService.mDisplayFrozen, mDisplayContent.mAppTransition.isTimeout());
-
+ if (mDisplayContent.mAppTransition.isTimeout()) {
+ return true;
+ }
final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
Display.DEFAULT_DISPLAY).getRotationAnimation();
- if (!mDisplayContent.mAppTransition.isTimeout()) {
- // Imagine the case where we are changing orientation due to an app transition, but a
- // previous orientation change is still in progress. We won't process the orientation
- // change for our transition because we need to wait for the rotation animation to
- // finish.
- // If we start the app transition at this point, we will interrupt it halfway with a
- // new rotation animation after the old one finally finishes. It's better to defer the
- // app transition.
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
- && mDisplayContent.getDisplayRotation().needsUpdate()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for screen rotation animation to finish");
+ // Imagine the case where we are changing orientation due to an app transition, but a
+ // previous orientation change is still in progress. We won't process the orientation
+ // change for our transition because we need to wait for the rotation animation to
+ // finish.
+ // If we start the app transition at this point, we will interrupt it halfway with a
+ // new rotation animation after the old one finally finishes. It's better to defer the
+ // app transition.
+ if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
+ && mDisplayContent.getDisplayRotation().needsUpdate()) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Delaying app transition for screen rotation animation to finish");
+ return false;
+ }
+ for (int i = 0; i < apps.size(); i++) {
+ WindowContainer wc = apps.valueAt(i);
+ final ActivityRecord activity = getAppFromContainer(wc);
+ if (activity == null) {
+ continue;
+ }
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
+ activity, activity.allDrawn, activity.startingDisplayed,
+ activity.startingMoved, activity.isRelaunching(),
+ activity.mStartingWindow);
+
+ final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
+ if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
return false;
}
- for (int i = 0; i < apps.size(); i++) {
- WindowContainer wc = apps.valueAt(i);
- final ActivityRecord activity = getAppFromContainer(wc);
- if (activity == null) {
- continue;
- }
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
- + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
- activity, activity.allDrawn, activity.startingDisplayed,
- activity.startingMoved, activity.isRelaunching(),
- activity.mStartingWindow);
+ if (allDrawn) {
+ outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN);
+ } else {
+ outReasons.put(activity,
+ activity.mStartingData instanceof SplashScreenStartingData
+ ? APP_TRANSITION_SPLASH_SCREEN
+ : APP_TRANSITION_SNAPSHOT);
+ }
+ }
+ // We also need to wait for the specs to be fetched, if needed.
+ if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true");
+ return false;
+ }
- final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
- if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
- return false;
- }
- if (allDrawn) {
- outReasons.put(activity, APP_TRANSITION_WINDOWS_DRAWN);
- } else {
- outReasons.put(activity,
- activity.mStartingData instanceof SplashScreenStartingData
- ? APP_TRANSITION_SPLASH_SCREEN
- : APP_TRANSITION_SNAPSHOT);
- }
- }
+ if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s",
+ mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
+ return false;
+ }
- // We also need to wait for the specs to be fetched, if needed.
- if (mDisplayContent.mAppTransition.isFetchingAppTransitionsSpecs()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "isFetchingAppTransitionSpecs=true");
- return false;
- }
+ // If the wallpaper is visible, we need to check it's ready too.
+ return !mWallpaperControllerLocked.isWallpaperVisible()
+ || mWallpaperControllerLocked.wallpaperTransitionReady();
+ }
- if (!mDisplayContent.mUnknownAppVisibilityController.allResolved()) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "unknownApps is not empty: %s",
- mDisplayContent.mUnknownAppVisibilityController.getDebugMessage());
- return false;
- }
+ private boolean transitionGoodToGoForTaskFragments() {
+ if (mDisplayContent.mAppTransition.isTimeout()) {
+ return true;
+ }
- // If the wallpaper is visible, we need to check it's ready too.
- boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() ||
- mWallpaperControllerLocked.wallpaperTransitionReady();
- if (wallpaperReady) {
- return true;
+ // Check all Tasks in this transition. This is needed because new TaskFragment created for
+ // launching activity may not be in the tracking lists, but we still want to wait for the
+ // activity launch to start the transition.
+ final ArraySet<Task> rootTasks = new ArraySet<>();
+ for (int i = mDisplayContent.mOpeningApps.size() - 1; i >= 0; i--) {
+ rootTasks.add(mDisplayContent.mOpeningApps.valueAt(i).getRootTask());
+ }
+ for (int i = mDisplayContent.mClosingApps.size() - 1; i >= 0; i--) {
+ rootTasks.add(mDisplayContent.mClosingApps.valueAt(i).getRootTask());
+ }
+ for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+ rootTasks.add(
+ findRootTaskFromContainer(mDisplayContent.mChangingContainers.valueAt(i)));
+ }
+
+ // Organized TaskFragment can be empty for two situations:
+ // 1. New created and is waiting for Activity launch. In this case, we want to wait for
+ // the Activity launch to trigger the transition.
+ // 2. Last Activity is just removed. In this case, we want to wait for organizer to
+ // remove the TaskFragment because it may also want to change other TaskFragments in
+ // the same transition.
+ for (int i = rootTasks.size() - 1; i >= 0; i--) {
+ final Task rootTask = rootTasks.valueAt(i);
+ if (rootTask == null) {
+ // It is possible that one activity may have been removed from the hierarchy. No
+ // need to check for this case.
+ continue;
+ }
+ final boolean notReady = rootTask.forAllLeafTaskFragments(taskFragment -> {
+ if (!taskFragment.isReadyToTransit()) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Organized TaskFragment is not ready= %s",
+ taskFragment);
+ return true;
+ }
+ return false;
+ });
+ if (notReady) {
+ return false;
}
- return false;
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index faeb4ba36748..2a8ac39ead8d 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -20,9 +20,11 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import android.annotation.NonNull;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
/**
@@ -65,6 +67,7 @@ class BLASTSyncEngine {
class SyncGroup {
final int mSyncId;
final TransactionReadyListener mListener;
+ final Runnable mOnTimeout;
boolean mReady = false;
final ArraySet<WindowContainer> mRootMembers = new ArraySet<>();
private SurfaceControl.Transaction mOrphanTransaction = null;
@@ -72,6 +75,12 @@ class BLASTSyncEngine {
private SyncGroup(TransactionReadyListener listener, int id) {
mSyncId = id;
mListener = listener;
+ mOnTimeout = () -> {
+ Slog.w(TAG, "Sync group " + mSyncId + " timeout");
+ synchronized (mWm.mGlobalLock) {
+ onTimeout();
+ }
+ };
}
/**
@@ -114,6 +123,7 @@ class BLASTSyncEngine {
}
mListener.onTransactionReady(mSyncId, merged);
mActiveSyncs.remove(mSyncId);
+ mWm.mH.removeCallbacks(mOnTimeout);
}
private void setReady(boolean ready) {
@@ -136,6 +146,17 @@ class BLASTSyncEngine {
void onCancelSync(WindowContainer wc) {
mRootMembers.remove(wc);
}
+
+ private void onTimeout() {
+ if (!mActiveSyncs.contains(mSyncId)) return;
+ for (int i = mRootMembers.size() - 1; i >= 0; --i) {
+ final WindowContainer<?> wc = mRootMembers.valueAt(i);
+ if (!wc.isSyncFinished()) {
+ Slog.i(TAG, "Unfinished container: " + wc);
+ }
+ }
+ finishNow();
+ }
}
private final WindowManagerService mWm;
@@ -147,13 +168,23 @@ class BLASTSyncEngine {
}
int startSyncSet(TransactionReadyListener listener) {
+ return startSyncSet(listener, WindowState.BLAST_TIMEOUT_DURATION);
+ }
+
+ int startSyncSet(TransactionReadyListener listener, long timeoutMs) {
final int id = mNextSyncId++;
final SyncGroup s = new SyncGroup(listener, id);
mActiveSyncs.put(id, s);
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", id, listener);
+ scheduleTimeout(s, timeoutMs);
return id;
}
+ @VisibleForTesting
+ void scheduleTimeout(SyncGroup s, long timeoutMs) {
+ mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
+ }
+
void addToSyncSet(int id, WindowContainer wc) {
mActiveSyncs.get(id).addToSync(wc);
}
@@ -166,6 +197,10 @@ class BLASTSyncEngine {
setReady(id, true);
}
+ boolean isReady(int id) {
+ return mActiveSyncs.get(id).mReady;
+ }
+
/**
* Aborts the sync (ie. it doesn't wait for ready or anything to finish)
*/
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 71a10df34d30..0afd87282783 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -20,6 +20,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVIT
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.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,13 +72,13 @@ class BackgroundLaunchProcessController {
}
boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName,
- boolean appSwitchAllowed, boolean isCheckingForFgsStart,
+ int appSwitchState, boolean isCheckingForFgsStart,
boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
long lastStopAppSwitchesTime, long lastActivityLaunchTime,
long lastActivityFinishTime) {
// If app switching is not allowed, we ignore all the start activity grace period
// exception so apps cannot start itself in onPause() after pressing home button.
- if (appSwitchAllowed) {
+ if (appSwitchState == APP_SWITCH_ALLOW) {
// Allow if any activity in the caller has either started or finished very recently, and
// it must be started or finished after last stop app switches time.
final long now = SystemClock.uptimeMillis();
@@ -111,7 +113,8 @@ class BackgroundLaunchProcessController {
return true;
}
// Allow if the caller has an activity in any foreground task.
- if (appSwitchAllowed && hasActivityInVisibleTask) {
+ if (hasActivityInVisibleTask
+ && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "[Process(" + pid
+ ")] Activity start allowed: process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index d52e9b608cdb..5a2cf17ffd18 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -28,16 +28,20 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.windowingModeToString;
+import static android.app.WindowConfigurationProto.WINDOWING_MODE;
+import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.LocaleList;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -111,6 +115,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
* This method should be used for getting settings applied in each particular level of the
* hierarchy.
*/
+ @NonNull
public Configuration getConfiguration() {
return mFullConfiguration;
}
@@ -170,11 +175,13 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
}
/** Returns requested override configuration applied to this configuration container. */
+ @NonNull
public Configuration getRequestedOverrideConfiguration() {
return mRequestedOverrideConfiguration;
}
/** Returns the resolved override configuration. */
+ @NonNull
Configuration getResolvedOverrideConfiguration() {
return mResolvedOverrideConfiguration;
}
@@ -203,6 +210,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
* Get merged override configuration from the top of the hierarchy down to this particular
* instance. This should be reported to client as override config.
*/
+ @NonNull
public Configuration getMergedOverrideConfiguration() {
return mMergedOverrideConfiguration;
}
@@ -507,7 +515,7 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
}
- /** Returns the activity type associated with the the configuration container. */
+ /** Returns the activity type associated with the configuration container. */
/*@WindowConfiguration.ActivityType*/
public int getActivityType() {
return mFullConfiguration.windowConfiguration.getActivityType();
@@ -541,20 +549,48 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
}
/**
+ * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
+ * @return true if any of the requested configuration has been updated.
+ */
+ public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+ mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
+ boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
+ nightMode);
+ boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
+ locales);
+ if (newNightModeSet || newLocalesSet) {
+ onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+ }
+ return newNightModeSet || newLocalesSet;
+ }
+
+ /**
* Overrides the night mode applied to this ConfigurationContainer.
* @return true if the nightMode has been changed.
*/
- public boolean setOverrideNightMode(int nightMode) {
+ private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) {
final int currentUiMode = mRequestedOverrideConfiguration.uiMode;
final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK;
final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode == validNightMode) {
return false;
}
- mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
- mRequestsTmpConfig.uiMode = validNightMode
+ requestsTmpConfig.uiMode = validNightMode
| (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK);
- onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
+ return true;
+ }
+
+ /**
+ * Overrides the locales applied to this ConfigurationContainer.
+ * @return true if the LocaleList has been changed.
+ */
+ private boolean setOverrideLocales(Configuration requestsTmpConfig,
+ @NonNull LocaleList overrideLocales) {
+ if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) {
+ return false;
+ }
+ requestsTmpConfig.setLocales(overrideLocales);
+ requestsTmpConfig.userSetLocale = true;
return true;
}
@@ -661,22 +697,40 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
@CallSuper
protected void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
- // Critical log level logs only visible elements to mitigate performance overheard
- if (logLevel != WindowTraceLogLevel.ALL && !mHasOverrideConfiguration) {
- return;
+ final long token = proto.start(fieldId);
+
+ if (logLevel == WindowTraceLogLevel.ALL || mHasOverrideConfiguration) {
+ mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION,
+ logLevel == WindowTraceLogLevel.CRITICAL);
}
- final long token = proto.start(fieldId);
- mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION,
- logLevel == WindowTraceLogLevel.CRITICAL);
+ // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't
+ // required to mitigate performance overhead
if (logLevel == WindowTraceLogLevel.ALL) {
mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */);
mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION,
false /* critical */);
}
+
+ if (logLevel == WindowTraceLogLevel.TRIM) {
+ // Required for Fass to automatically detect pip transitions in Winscope traces
+ dumpDebugWindowingMode(proto);
+ }
+
proto.end(token);
}
+ private void dumpDebugWindowingMode(ProtoOutputStream proto) {
+ final long fullConfigToken = proto.start(FULL_CONFIGURATION);
+ final long windowConfigToken = proto.start(WINDOW_CONFIGURATION);
+
+ int windowingMode = mFullConfiguration.windowConfiguration.getWindowingMode();
+ proto.write(WINDOWING_MODE, windowingMode);
+
+ proto.end(windowConfigToken);
+ proto.end(fullConfigToken);
+ }
+
/**
* Dumps the names of this container children in the input print writer indenting each
* level with the input prefix.
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index baa27e34d625..99f6fd4771b7 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -495,8 +495,10 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
DisplayAreaInfo getDisplayAreaInfo() {
- DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(),
+ final DisplayAreaInfo info = new DisplayAreaInfo(mRemoteToken.toWindowContainerToken(),
getDisplayContent().getDisplayId(), mFeatureId);
+ final RootDisplayArea root = getRootDisplayArea();
+ info.rootDisplayAreaId = root == null ? getDisplayContent().mFeatureId : root.mFeatureId;
info.configuration.setTo(getConfiguration());
return info;
}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 47d7c9d1279d..3d7ac6c1a3f8 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -25,6 +25,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -135,12 +136,6 @@ import java.util.function.BiFunction;
*/
class DisplayAreaPolicyBuilder {
- /**
- * Key to specify the {@link RootDisplayArea} to attach the window to. Should be used by the
- * function passed in from {@link #setSelectRootForWindowFunc(BiFunction)}
- */
- static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id";
-
@Nullable private HierarchyBuilder mRootHierarchyBuilder;
private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders =
new ArrayList<>();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 8a9d5d438a47..42c6dd43ebce 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -44,6 +44,8 @@ import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static android.view.Display.STATE_UNKNOWN;
+import static android.view.Display.isSuspendedState;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
@@ -62,7 +64,6 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
@@ -79,6 +80,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
@@ -90,13 +92,17 @@ 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_LAYER_MIRRORING;
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.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;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
@@ -113,13 +119,15 @@ import static com.android.server.wm.DisplayContentProto.IME_POLICY;
import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET;
import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
+import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
+import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
+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_RECENTS;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.DISPLAY_CONTENT;
@@ -128,7 +136,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -136,7 +143,6 @@ import static com.android.server.wm.WindowManagerService.H.REPORT_HARD_KEYBOARD_
import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT;
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_PLACING_SURFACES;
-import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_ASSIGN_LAYERS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -204,6 +210,7 @@ import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.MagnificationSpec;
import android.view.PrivacyIndicatorBounds;
import android.view.RemoteAnimationDefinition;
@@ -298,7 +305,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* The direct child layer of the display to put all non-overlay windows. This is also used for
* screen rotation animation so that there is a parent layer to put the animation leash.
*/
- private final SurfaceControl mWindowingLayer;
+ private SurfaceControl mWindowingLayer;
+
+ /**
+ * The window token of the layer of the hierarchy to mirror, or null if this DisplayContent
+ * is not being used for layer mirroring.
+ */
+ @VisibleForTesting IBinder mTokenToMirror = null;
+
+ /**
+ * The surface for mirroring the contents of this hierarchy, or null if layer mirroring is
+ * temporarily disabled.
+ */
+ private SurfaceControl mMirroredSurface = null;
+
+ /**
+ * The last bounds of the DisplayArea to mirror.
+ */
+ private Rect mLastMirroredDisplayAreaBounds = null;
// Contains all IME window containers. Note that the z-ordering of the IME windows will depend
// on the IME target. We mainly have this container grouping so we can keep track of all the IME
@@ -308,7 +332,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final ImeContainer mImeWindowsContainer = new ImeContainer(mWmService);
@VisibleForTesting
- final DisplayAreaPolicy mDisplayAreaPolicy;
+ DisplayAreaPolicy mDisplayAreaPolicy;
private WindowState mTmpWindow;
private boolean mUpdateImeTarget;
@@ -364,6 +388,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
boolean mIsSizeForced = false;
/**
+ * Overridden display size and metrics to activity window bounds. Set via
+ * "adb shell wm set-sandbox-display-apis". Default to true, since only disable for debugging.
+ * @see WindowManagerService#setSandboxDisplayApis(int, boolean)
+ */
+ private boolean mSandboxDisplayApis = true;
+
+ /**
* Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity}
* but can be set from Settings or via shell command "adb shell wm density".
* @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int)
@@ -428,6 +459,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Accessed directly by all users.
private boolean mLayoutNeeded;
int pendingLayoutChanges;
+ boolean mLayoutAndAssignWindowLayersScheduled;
/**
* Used to gate application window layout until we have sent the complete configuration.
@@ -500,11 +532,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
WindowState mCurrentFocus = null;
/**
- * The last focused window that we've notified the client that the focus is changed.
- */
- WindowState mLastFocus = null;
-
- /**
* The foreground app of this display. Windows below this app cannot be the focused window. If
* the user taps on the area outside of the task of the focused app, we will notify AM about the
* new task the user wants to interact with.
@@ -555,6 +582,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* Specifies the count to determine whether to defer updating the IME target until ready.
*/
private int mDeferUpdateImeTargetCount;
+ private boolean mUpdateImeRequestedWhileDeferred;
private MagnificationSpec mMagnificationSpec;
@@ -692,6 +720,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// well and thus won't change the top resumed / focused record
boolean mDontMoveToTop;
+ private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
@@ -773,6 +803,21 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTmpWindow = null;
return true;
}
+
+ // If the candidate activity is currently being embedded in the focused task, the
+ // activity cannot be focused unless it is on the same TaskFragment as the focusedApp's.
+ TaskFragment parent = activity.getTaskFragment();
+ if (parent != null && parent.isEmbedded()) {
+ Task hostTask = focusedApp.getTask();
+ if (hostTask.isEmbedded()) {
+ // Use the hosting task if the current task is embedded.
+ hostTask = hostTask.getParent().asTaskFragment().getTask();
+ }
+ if (activity.isDescendantOf(hostTask)
+ && activity.getTaskFragment() != focusedApp.getTaskFragment()) {
+ return false;
+ }
+ }
}
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);
@@ -961,8 +1006,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final boolean committed = winAnimator.commitFinishDrawingLocked();
if (isDefaultDisplay && committed) {
if (w.hasWallpaper()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "First draw done in potential wallpaper target " + w);
+ ProtoLog.v(WM_DEBUG_WALLPAPER,
+ "First draw done in potential wallpaper target %s", w);
mWallpaperMayChange = true;
pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
@@ -1060,52 +1105,91 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mDividerControllerLocked = new DockedTaskDividerController(this);
mPinnedTaskController = new PinnedTaskController(mWmService, this);
+ final Transaction pendingTransaction = getPendingTransaction();
+ configureSurfaces(pendingTransaction);
+ pendingTransaction.apply();
+
+ // Sets the display content for the children.
+ onDisplayChanged(this);
+ updateDisplayAreaOrganizers();
+
+ mInputMonitor = new InputMonitor(mWmService, this);
+ mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
+
+ if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
+
+ mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
+ }
+
+ @Override
+ void migrateToNewSurfaceControl(Transaction t) {
+ t.remove(mSurfaceControl);
+
+ mLastSurfacePosition.set(0, 0);
+
+ configureSurfaces(t);
+
+ for (int i = 0; i < mChildren.size(); i++) {
+ SurfaceControl sc = mChildren.get(i).getSurfaceControl();
+ if (sc != null) {
+ t.reparent(sc, mSurfaceControl);
+ }
+ }
+
+ scheduleAnimation();
+ }
+
+ /**
+ * Configures the surfaces hierarchy for DisplayContent
+ * This method always recreates the main surface control but reparents the children
+ * if they are already created.
+ * @param transaction as part of which to perform the configuration
+ */
+ private void configureSurfaces(Transaction transaction) {
final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
.setOpaque(true)
.setContainerLayer()
.setCallsite("DisplayContent");
- mSurfaceControl = b.setName("Root").setContainerLayer().build();
+ mSurfaceControl = b.setName(getName()).setContainerLayer().build();
- // Setup the policy and build the display area hierarchy.
- mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
- mWmService, this /* content */, this /* root */, mImeWindowsContainer);
+ if (mDisplayAreaPolicy == null) {
+ // Setup the policy and build the display area hierarchy.
+ // Build the hierarchy only after creating the surface so it is reparented correctly
+ mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
+ mWmService, this /* content */, this /* root */,
+ mImeWindowsContainer);
+ }
final List<DisplayArea<? extends WindowContainer>> areas =
mDisplayAreaPolicy.getDisplayAreas(FEATURE_WINDOWED_MAGNIFICATION);
final DisplayArea<?> area = areas.size() == 1 ? areas.get(0) : null;
+
if (area != null && area.getParent() == this) {
// The windowed magnification area should contain all non-overlay windows, so just use
// it as the windowing layer.
mWindowingLayer = area.mSurfaceControl;
+ transaction.reparent(mWindowingLayer, mSurfaceControl);
} else {
// Need an additional layer for screen level animation, so move the layer containing
// the windows to the new root.
mWindowingLayer = mSurfaceControl;
mSurfaceControl = b.setName("RootWrapper").build();
- getPendingTransaction().reparent(mWindowingLayer, mSurfaceControl)
+ transaction.reparent(mWindowingLayer, mSurfaceControl)
.show(mWindowingLayer);
}
- mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
+ if (mOverlayLayer == null) {
+ mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
+ } else {
+ transaction.reparent(mOverlayLayer, mSurfaceControl);
+ }
- getPendingTransaction()
+ transaction
.setLayer(mSurfaceControl, 0)
.setLayerStack(mSurfaceControl, mDisplayId)
.show(mSurfaceControl)
.setLayer(mOverlayLayer, Integer.MAX_VALUE)
.show(mOverlayLayer);
- getPendingTransaction().apply();
-
- // Sets the display content for the children.
- onDisplayChanged(this);
- updateDisplayAreaOrganizers();
-
- mInputMonitor = new InputMonitor(mWmService, this);
- mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
-
- if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
-
- mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
}
boolean isReady() {
@@ -1236,17 +1320,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// removing from parent.
token.getParent().removeChild(token);
}
- if (token.hasChild(prevDc.mLastFocus)) {
- // If the reparent window token contains previous display's last focus window, means
- // it will end up to gain window focus on the target display, so it should not be
- // notified that it lost focus from the previous display.
- prevDc.mLastFocus = null;
- }
}
addWindowToken(token.token, token);
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
final int prevDisplayId = prevDc != null ? prevDc.getDisplayId() : INVALID_DISPLAY;
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(prevDisplayId,
getDisplayId());
@@ -1352,11 +1430,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final Configuration currentDisplayConfig = getConfiguration();
mTmpConfiguration.setTo(currentDisplayConfig);
computeScreenConfiguration(mTmpConfiguration);
- configChanged |= currentDisplayConfig.diff(mTmpConfiguration) != 0;
+ final int changes = currentDisplayConfig.diff(mTmpConfiguration);
+ configChanged |= changes != 0;
if (configChanged) {
mWaitingForConfig = true;
- mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ requestChangeTransitionIfNeeded(changes);
+ } else {
+ mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
+ }
sendNewConfiguration();
}
@@ -1472,7 +1555,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
} else if (currentConfig != null
// If waiting for a remote rotation, don't prematurely update configuration.
&& !(mDisplayRotation.isWaitingForRemoteRotation()
- || mAtmService.getTransitionController().isCollecting(this))) {
+ || mTransitionController.isCollecting(this))) {
// No obvious action we need to take, but if our current state mismatches the
// activity manager's, update it, disregarding font scale, which should remain set
// to the value of the previous configuration.
@@ -1570,11 +1653,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// to cover the activity configuration change.
return false;
}
- if (r.attachedToProcess() && mayImeShowOnLaunchingActivity(r)) {
- // Currently it is unknown that when will IME window be ready. Reject the case to
- // avoid flickering by showing IME in inconsistent orientation.
- return false;
- }
if (checkOpening) {
if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
// Apply normal rotation animation in case of the activity set different requested
@@ -1623,7 +1701,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/** Returns {@code true} if the IME is possible to show on the launching activity. */
- private boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) {
+ boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) {
final WindowState win = r.findMainWindow();
if (win == null) {
return false;
@@ -1871,8 +1949,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
private void applyRotation(final int oldRotation, final int rotation) {
mDisplayRotation.applyCurrentRotation(rotation);
- final boolean shellTransitions =
- mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null;
+ final boolean shellTransitions = mTransitionController.getTransitionPlayer() != null;
final boolean rotateSeamlessly =
mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;
final Transaction transaction =
@@ -1921,10 +1998,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
}
-
- if (mWmService.mAccessibilityController != null) {
- mWmService.mAccessibilityController.onRotationChanged(this);
- }
}
void configureDisplayPolicy() {
@@ -1964,16 +2037,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
- final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
+ final Point appSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode,
displayCutout);
mDisplayInfo.rotation = rotation;
mDisplayInfo.logicalWidth = dw;
mDisplayInfo.logicalHeight = dh;
mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
- mDisplayInfo.appWidth = appWidth;
- mDisplayInfo.appHeight = appHeight;
+ mDisplayInfo.appWidth = appSize.x;
+ mDisplayInfo.appHeight = appSize.y;
if (isDefaultDisplay) {
mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
@@ -2102,24 +2173,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Compute configuration related to application without changing current display. */
private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
int rotation, int uiMode, DisplayCutout displayCutout) {
- final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
+ final Point appSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode,
displayCutout);
mDisplayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
final int leftInset = mTmpRect.left;
final int topInset = mTmpRect.top;
// AppBounds at the root level should mirror the app screen size.
outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */,
- leftInset + appWidth /* right */, topInset + appHeight /* bottom */);
+ leftInset + appSize.x /* right */, topInset + appSize.y /* bottom */);
outConfig.windowConfiguration.setRotation(rotation);
outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
final float density = mDisplayMetrics.density;
- outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation,
- uiMode, displayCutout) / density);
- outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation,
- uiMode, displayCutout) / density);
+ final Point configSize = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, uiMode,
+ displayCutout);
+ outConfig.screenWidthDp = (int) (configSize.x / density);
+ outConfig.screenHeightDp = (int) (configSize.y / density);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
@@ -2258,10 +2327,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
DisplayMetrics dm, int dw, int dh) {
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
rotation).getDisplayCutout();
- dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
- displayCutout);
- dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
+ final Point nonDecorSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode,
displayCutout);
+ dm.noncompatWidthPixels = nonDecorSize.x;
+ dm.noncompatHeightPixels = nonDecorSize.y;
float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
if (curSize == 0 || size < curSize) {
@@ -2313,12 +2382,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
rotation).getDisplayCutout();
// Get the app screen size at this rotation.
- int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout);
- int h = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout);
+ final Point size = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode,
+ displayCutout);
// Compute the screen layout size class for this rotation.
- int longSize = w;
- int shortSize = h;
+ int longSize = size.x;
+ int shortSize = size.y;
if (longSize < shortSize) {
int tmp = longSize;
longSize = shortSize;
@@ -2333,21 +2402,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
int uiMode, int dw, int dh) {
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
rotation).getDisplayCutout();
- final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
+ final Point size = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, uiMode,
displayCutout);
- if (width < displayInfo.smallestNominalAppWidth) {
- displayInfo.smallestNominalAppWidth = width;
+ if (size.x < displayInfo.smallestNominalAppWidth) {
+ displayInfo.smallestNominalAppWidth = size.x;
}
- if (width > displayInfo.largestNominalAppWidth) {
- displayInfo.largestNominalAppWidth = width;
+ if (size.x > displayInfo.largestNominalAppWidth) {
+ displayInfo.largestNominalAppWidth = size.x;
}
- final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
- displayCutout);
- if (height < displayInfo.smallestNominalAppHeight) {
- displayInfo.smallestNominalAppHeight = height;
+ if (size.y < displayInfo.smallestNominalAppHeight) {
+ displayInfo.smallestNominalAppHeight = size.y;
}
- if (height > displayInfo.largestNominalAppHeight) {
- displayInfo.largestNominalAppHeight = height;
+ if (size.y > displayInfo.largestNominalAppHeight) {
+ displayInfo.largestNominalAppHeight = size.y;
}
}
@@ -2472,6 +2539,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Update IME parent if needed.
updateImeParent();
+ // Update mirroring surface for MediaProjection, if this DisplayContent is being used
+ // for layer mirroring.
+ if (isCurrentlyMirroring() && mLastMirroredDisplayAreaBounds != null) {
+ // Mirroring has already begun, but update mirroring since the display is now on.
+ final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer(
+ mTokenToMirror);
+ if (wc == null) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to retrieve window container to update layer mirroring for "
+ + "display %d",
+ mDisplayId);
+ return;
+ }
+
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d was already layer mirroring, so apply transformations if necessary",
+ mDisplayId);
+ // Retrieve the size of the DisplayArea to mirror, and continue with the update
+ // if the bounds or orientation has changed.
+ final Rect displayAreaBounds = wc.getDisplayContent().getBounds();
+ int displayAreaOrientation = wc.getDisplayContent().getOrientation();
+ if (!mLastMirroredDisplayAreaBounds.equals(displayAreaBounds)
+ || lastOrientation != displayAreaOrientation) {
+ Point surfaceSize = fetchSurfaceSizeIfPresent();
+ if (surfaceSize != null) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Going ahead with updating layer mirroring for display %d to new "
+ + "bounds %s and/or orientation %d.",
+ mDisplayId, displayAreaBounds, displayAreaOrientation);
+ updateMirroredSurface(mWmService.mTransactionFactory.get(),
+ displayAreaBounds, surfaceSize);
+ } else {
+ // If the surface removed, do nothing. We will handle this via onDisplayChanged
+ // (the display will be off if the surface is removed).
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to update layer mirroring for display %d to new bounds %s"
+ + " and/or orientation %d, since the surface is not available.",
+ mDisplayId, displayAreaBounds, displayAreaOrientation);
+ }
+ }
+ }
+
if (lastOrientation != getConfiguration().orientation) {
getMetricsLogger().write(
new LogMaker(MetricsEvent.ACTION_PHONE_ORIENTATION_CHANGED)
@@ -2492,7 +2601,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
boolean isVisibleRequested() {
- return isVisible();
+ return isVisible() && !mRemoved && !mRemoving;
}
@Override
@@ -2986,7 +3095,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
void removeIfPossible() {
- if (isAnimating(TRANSITION | PARENTS)) {
+ if (isAnimating(TRANSITION | PARENTS)
+ // isAnimating is a legacy transition query and will be removed, so also add a
+ // check for whether this is in a shell-transition when not using legacy.
+ || mTransitionController.inTransition()) {
mDeferredRemoval = true;
return;
}
@@ -3016,6 +3128,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mOverlayLayer.release();
mInputMonitor.onDisplayRemoved();
mWmService.mDisplayNotificationController.dispatchDisplayRemoved(this);
+ mWmService.mAccessibilityController.onDisplayRemoved(mDisplayId);
} finally {
mDisplayReady = false;
}
@@ -3087,6 +3200,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mScreenRotationAnimation;
}
+ /**
+ * Requests to start a transition for the display configuration change. The given changes must
+ * be non-zero. This method is no-op if the display has been collected.
+ */
+ void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes) {
+ final TransitionController controller = mTransitionController;
+ if (controller.isCollecting()) {
+ if (!controller.isCollecting(this)) {
+ controller.collect(this);
+ }
+ return;
+ }
+ final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, this);
+ if (t != null) {
+ if (getRotation() != getWindowConfiguration().getRotation()) {
+ mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
+ controller.mTransitionMetricsReporter.associate(t,
+ startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
+ }
+ t.setKnownConfigChanges(this, changes);
+ }
+ }
+
+ /** If the display is in transition, there should be a screenshot covering it. */
+ @Override
+ boolean inTransition() {
+ return mScreenRotationAnimation != null || super.inTransition();
+ }
+
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
@@ -3107,7 +3249,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
screenRotationAnimation.dumpDebug(proto, SCREEN_ROTATION_ANIMATION);
}
mDisplayFrames.dumpDebug(proto, DISPLAY_FRAMES);
- mAppTransition.dumpDebug(proto, APP_TRANSITION);
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ mTransitionController.dumpDebugLegacy(proto, APP_TRANSITION);
+ } else {
+ mAppTransition.dumpDebug(proto, APP_TRANSITION);
+ }
if (mFocusedApp != null) {
mFocusedApp.writeNameToProto(proto, FOCUSED_APP);
}
@@ -3130,6 +3276,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
proto.write(FOCUSED_ROOT_TASK_ID, INVALID_TASK_ID);
}
proto.write(DISPLAY_READY, isReady());
+ proto.write(IS_SLEEPING, isSleeping());
+ for (int i = 0; i < mAllSleepTokens.size(); ++i) {
+ mAllSleepTokens.get(i).writeTagToProto(proto, SLEEP_TOKENS);
+ }
+
if (mImeLayeringTarget != null) {
mImeLayeringTarget.dumpDebug(proto, INPUT_METHOD_TARGET, logLevel);
}
@@ -3195,9 +3346,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
- if (mLastFocus != mCurrentFocus) {
- pw.print(" mLastFocus="); pw.println(mLastFocus);
- }
pw.print(" mFocusedApp="); pw.println(mFocusedApp);
if (mFixedRotationLaunchingApp != null) {
pw.println(" mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
@@ -3289,7 +3437,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public String toString() {
- return "Display " + mDisplayId + " info=" + mDisplayInfo + " rootTasks=" + mChildren;
+ return "Display{#" + mDisplayId + " state=" + Display.stateToString(mDisplayInfo.state)
+ + " size=" + mDisplayInfo.logicalWidth + "x" + mDisplayInfo.logicalHeight
+ + " " + Surface.rotationToString(mDisplayInfo.rotation) + "}";
}
String getName() {
@@ -3428,15 +3578,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- onWindowFocusChanged(oldFocus, newFocus);
-
- int focusChanged = getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
+ getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
if (imWindowChanged && oldFocus != mInputMethodWindow) {
// Focus of the input method window changed. Perform layout if needed.
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
performLayout(true /*initial*/, updateInputWindows);
- focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT;
} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
// Client will do the layout, but we need to assign layers
// for handleNewWindowLocked() below.
@@ -3444,16 +3591,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
- // The change in focus caused us to need to do a layout. Okay.
- setLayoutNeeded();
- if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
- performLayout(true /*initial*/, updateInputWindows);
- } else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) {
- mWmService.mRoot.performSurfacePlacement();
- }
- }
-
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
// If we defer assigning layers, then the caller is responsible for doing this part.
getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
@@ -3474,13 +3611,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// focused one starts firing events.
// TODO(b/151179149) investigate what info accessibility service needs before input can
// dispatch focus to clients.
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mH.sendMessage(PooledLambda.obtainMessage(
this::updateAccessibilityOnWindowFocusChanged,
mWmService.mAccessibilityController));
}
- mLastFocus = mCurrentFocus;
return true;
}
@@ -3488,20 +3624,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
accessibilityController.onWindowFocusChangedNot(getDisplayId());
}
- private static void onWindowFocusChanged(WindowState oldFocus, WindowState newFocus) {
- final Task focusedTask = newFocus != null ? newFocus.getTask() : null;
- final Task unfocusedTask = oldFocus != null ? oldFocus.getTask() : null;
- if (focusedTask == unfocusedTask) {
- return;
- }
- if (focusedTask != null) {
- focusedTask.onWindowFocusChanged(true /* hasFocus */);
- }
- if (unfocusedTask != null) {
- unfocusedTask.onWindowFocusChanged(false /* hasFocus */);
- }
- }
-
/**
* Set the new focused app to this display.
*
@@ -3525,7 +3647,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "setFocusedApp %s displayId=%d Callers=%s",
newFocus, getDisplayId(), Debug.getCallers(4));
+ final Task oldTask = mFocusedApp != null ? mFocusedApp.getTask() : null;
+ final Task newTask = newFocus != null ? newFocus.getTask() : null;
mFocusedApp = newFocus;
+ if (oldTask != newTask) {
+ if (oldTask != null) oldTask.onAppFocusChanged(false);
+ if (newTask != null) newTask.onAppFocusChanged(true);
+ }
+
getInputMonitor().setFocusedAppLw(newFocus);
updateTouchExcludeRegion();
return true;
@@ -3666,6 +3795,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final WindowState curTarget = mImeLayeringTarget;
if (!canUpdateImeTarget()) {
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Defer updating IME target");
+ mUpdateImeRequestedWhileDeferred = true;
return curTarget;
}
@@ -3716,7 +3846,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
boolean shouldImeAttachedToApp() {
- return isImeControlledByApp()
+ // Force attaching IME to the display when magnifying, or it would be magnified with
+ // target app together.
+ final boolean allowAttachToApp = (mMagnificationSpec == null);
+
+ return allowAttachToApp && isImeControlledByApp()
&& mImeLayeringTarget != null
&& mImeLayeringTarget.mActivityRecord != null
&& mImeLayeringTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
@@ -3798,6 +3932,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
}
+ /** @see WindowManagerInternal#onToggleImeRequested */
+ void onShowImeRequested() {
+ if (mImeLayeringTarget == null || mInputMethodWindow == null) {
+ return;
+ }
+ // If IME window will be shown on the rotated activity, share the transformed state to
+ // IME window so it can compute rotated frame with rotated configuration.
+ if (mImeLayeringTarget.mToken.isFixedRotationTransforming()) {
+ mInputMethodWindow.mToken.linkFixedRotationTransform(mImeLayeringTarget.mToken);
+ // Hide the window until the rotation is done to avoid intermediate artifacts if the
+ // parent surface of IME container is changed.
+ if (mFadeRotationAnimationController != null) {
+ mFadeRotationAnimationController.hideImmediately(mInputMethodWindow.mToken);
+ }
+ }
+ }
+
@VisibleForTesting
void setImeLayeringTarget(WindowState target) {
mImeLayeringTarget = target;
@@ -3973,12 +4124,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void updateImeInputAndControlTarget(WindowState target) {
if (mImeInputTarget != target) {
ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
- if (target != null && target.mActivityRecord != null) {
- target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
- }
setImeInputTarget(target);
+ mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController
+ .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
updateImeControlTarget();
}
+ // Unfreeze IME insets after the new target updated, in case updateAboveInsetsState may
+ // deliver unrelated IME insets change to the non-IME requester.
+ if (target != null && target.mActivityRecord != null) {
+ target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
+ }
}
void updateImeControlTarget() {
@@ -4010,10 +4165,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final SurfaceControl newParent = computeImeParent();
if (newParent != null && newParent != mInputMethodSurfaceParent) {
mInputMethodSurfaceParent = newParent;
- getPendingTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent);
+ getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent);
// When surface parent is removed, the relative layer will also be removed. We need to
// do a force update to make sure there is a layer set for the new parent.
- assignRelativeLayerForIme(getPendingTransaction(), true /* forceUpdate */);
+ assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
scheduleAnimation();
}
}
@@ -4038,14 +4193,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
@VisibleForTesting
SurfaceControl computeImeParent() {
- // Force attaching IME to the display when magnifying, or it would be magnified with
- // target app together.
- final boolean allowAttachToApp = (mMagnificationSpec == null);
-
// Attach it to app if the target is part of an app and such app is covering the entire
// screen. If it's not covering the entire screen the IME might extend beyond the apps
// bounds.
- if (allowAttachToApp && shouldImeAttachedToApp()) {
+ if (shouldImeAttachedToApp()) {
if (mImeLayeringTarget.mActivityRecord != mImeInputTarget.mActivityRecord) {
// Do not change parent if the window hasn't requested IME.
return null;
@@ -4226,7 +4377,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG_WM, "Desired input method target: " + imFocus);
Slog.i(TAG_WM, "Current focus: " + mCurrentFocus + " displayId=" + mDisplayId);
- Slog.i(TAG_WM, "Last focus: " + mLastFocus + " displayId=" + mDisplayId);
}
if (DEBUG_INPUT_METHOD) {
@@ -4351,6 +4501,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
true /* inTraversal, must call performTraversalInTrans... below */);
}
+ // If the display now has content, or no longer has content, update layer mirroring.
+ updateMirroring();
final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
if (wallpaperVisible != mLastWallpaperVisible) {
@@ -4516,6 +4668,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ // clear first just in case.
+ mTmpActivityList.clear();
// Time to remove any exiting applications?
forAllRootTasks(task -> {
final ArrayList<ActivityRecord> activities = task.mExitingActivities;
@@ -4523,16 +4677,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final ActivityRecord activity = activities.get(j);
if (!activity.hasVisible && !mDisplayContent.mClosingApps.contains(activity)
&& (!activity.mIsExiting || activity.isEmpty())) {
- // Make sure there is no animation running on this activity, so any windows
- // associated with it will be removed as soon as their animations are
- // complete.
- cancelAnimation();
- ProtoLog.v(WM_DEBUG_ADD_REMOVE,
- "performLayout: Activity exiting now removed %s", activity);
- activity.removeIfPossible();
+ mTmpActivityList.add(activity);
}
}
});
+ if (!mTmpActivityList.isEmpty()) {
+ // Make sure there is no animation running on this activity, so any windows
+ // associated with it will be removed as soon as their animations are
+ // complete.
+ cancelAnimation();
+ }
+ for (int i = 0; i < mTmpActivityList.size(); ++i) {
+ final ActivityRecord activity = mTmpActivityList.get(i);
+ ProtoLog.v(WM_DEBUG_ADD_REMOVE,
+ "performLayout: Activity exiting now removed %s", activity);
+ activity.removeIfPossible();
+ }
+ // Clear afterwards so we don't hold references.
+ mTmpActivityList.clear();
}
@Override
@@ -4541,12 +4703,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.requestTraversal();
}
+ @Override
boolean okToDisplay() {
- return okToDisplay(false);
- }
-
- boolean okToDisplay(boolean ignoreFrozen) {
- return okToDisplay(ignoreFrozen, false /* ignoreScreenOn */);
+ return okToDisplay(false /* ignoreFrozen */, false /* ignoreScreenOn */);
}
boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) {
@@ -4558,18 +4717,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return mDisplayInfo.state == Display.STATE_ON;
}
- boolean okToAnimate() {
- return okToAnimate(false);
- }
-
- boolean okToAnimate(boolean ignoreFrozen) {
- return okToAnimate(ignoreFrozen, false /* ignoreScreenOn */);
- }
-
+ @Override
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
return okToDisplay(ignoreFrozen, ignoreScreenOn)
&& (mDisplayId != DEFAULT_DISPLAY
- || mWmService.mPolicy.okToAnimate(ignoreScreenOn));
+ || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+ && getDisplayPolicy().isScreenOnFully();
}
static final class TaskForResizePointSearchResult {
@@ -4603,7 +4756,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return true;
}
- if (task.isOrganized()) {
+ // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove
+ // the special case when it does.
+ if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
return true;
}
@@ -4700,7 +4855,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// WindowState#applyImeWindowsIfNeeded} in case of any state mismatch.
return dc.mImeLayeringTarget != null
&& (!dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
- || dc.mImeLayeringTarget.getTask() == null);
+ || dc.mImeLayeringTarget.getTask() == null)
+ // Make sure that the IME window won't be skipped to report that it has
+ // completed the orientation change.
+ && !dc.mWmService.mDisplayFrozen;
}
/** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */
@@ -4850,15 +5008,24 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Keep IME window in surface parent as long as app's starting window
// exists so it get's layered above the starting window.
if (imeTarget != null && !(imeTarget.mActivityRecord != null
- && imeTarget.mActivityRecord.hasStartingWindow()) && (
- !(imeTarget.inMultiWindowMode()
- || imeTarget.mToken.isAppTransitioning()) && (
- imeTarget.getSurfaceControl() != null))) {
- mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
- // TODO: We need to use an extra level on the app surface to ensure
- // this is always above SurfaceView but always below attached window.
- 1, forceUpdate);
- } else if (mInputMethodSurfaceParent != null) {
+ && imeTarget.mActivityRecord.hasStartingWindow())) {
+ final WindowToken imeControlTargetToken =
+ mImeControlTarget != null && mImeControlTarget.getWindow() != null
+ ? mImeControlTarget.getWindow().mToken : null;
+ final boolean canImeTargetSetRelativeLayer = imeTarget.getSurfaceControl() != null
+ && imeTarget.mToken == imeControlTargetToken
+ && !imeTarget.inMultiWindowMode()
+ && imeTarget.mToken.getActivity(app -> app.isAnimating(TRANSITION | PARENTS,
+ ANIMATION_TYPE_ALL & ~ANIMATION_TYPE_RECENTS)) == null;
+ if (canImeTargetSetRelativeLayer) {
+ mImeWindowsContainer.assignRelativeLayer(t, imeTarget.getSurfaceControl(),
+ // TODO: We need to use an extra level on the app surface to ensure
+ // this is always above SurfaceView but always below attached window.
+ 1, forceUpdate);
+ return;
+ }
+ }
+ if (mInputMethodSurfaceParent != null) {
// The IME surface parent may not be its window parent's surface
// (@see #computeImeParent), so set relative layer here instead of letting the window
// parent to assign layer.
@@ -4904,6 +5071,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* Increment the deferral count to determine whether to update the IME target.
*/
void deferUpdateImeTarget() {
+ if (mDeferUpdateImeTargetCount == 0) {
+ mUpdateImeRequestedWhileDeferred = false;
+ }
mDeferUpdateImeTargetCount++;
}
@@ -4917,7 +5087,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mDeferUpdateImeTargetCount--;
- if (mDeferUpdateImeTargetCount == 0) {
+ if (mDeferUpdateImeTargetCount == 0 && mUpdateImeRequestedWhileDeferred) {
computeImeTarget(true /* updateImeTarget */);
}
}
@@ -4962,10 +5132,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ /**
+ * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
+ */
+ @Deprecated
void prepareAppTransition(@WindowManager.TransitionType int transit) {
prepareAppTransition(transit, 0 /* flags */);
}
+ /**
+ * @deprecated new transition should use {@link #requestTransitionAndLegacyPrepare(int, int)}
+ */
+ @Deprecated
void prepareAppTransition(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
final boolean prepared = mAppTransition.prepareAppTransition(transit, flags);
@@ -4978,26 +5156,27 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* Helper that both requests a transition (using the new transition system) and prepares
* the legacy transition system. Use this when both systems have the same start-point.
*
- * @see TransitionController#requestTransitionIfNeeded(int, int, WindowContainer)
+ * @see TransitionController#requestTransitionIfNeeded(int, int, WindowContainer,
+ * WindowContainer)
* @see AppTransition#prepareAppTransition
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
prepareAppTransition(transit, flags);
- mAtmService.getTransitionController().requestTransitionIfNeeded(transit, flags,
- null /* trigger */);
+ mTransitionController.requestTransitionIfNeeded(transit, flags,
+ null /* trigger */, this);
}
/** @see #requestTransitionAndLegacyPrepare(int, int) */
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@Nullable WindowContainer trigger) {
prepareAppTransition(transit);
- mAtmService.getTransitionController().requestTransitionIfNeeded(transit, 0 /* flags */,
- trigger);
+ mTransitionController.requestTransitionIfNeeded(transit, 0 /* flags */,
+ trigger, this);
}
void executeAppTransition() {
- mAtmService.getTransitionController().setReady();
+ mTransitionController.setReady(this);
if (mAppTransition.isTransitionSet()) {
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
"Execute app transition: %s, displayId: %d Callers=%s",
@@ -5007,6 +5186,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ void cancelAppTransition() {
+ if (!mAppTransition.isTransitionSet() || mAppTransition.isRunning()) return;
+ mAppTransition.abort();
+ }
+
/**
* Update pendingLayoutChanges after app transition has finished.
*/
@@ -5026,9 +5210,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
onAppTransitionDone();
changes |= FINISH_LAYOUT_REDO_LAYOUT;
- if (DEBUG_WALLPAPER_LIGHT) {
- Slog.v(TAG_WM, "Wallpaper layer changed: assigning layers + relayout");
- }
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper layer changed: assigning layers + relayout");
computeImeTarget(true /* updateImeTarget */);
mWallpaperMayChange = true;
// Since the window list has been rebuilt, focus might have to be recomputed since the
@@ -5040,6 +5222,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Check if pending app transition is for activity / task launch. */
boolean isNextTransitionForward() {
+ // TODO(b/191375840): decouple "forwardness" from transition system.
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ @WindowManager.TransitionType int type =
+ mTransitionController.getCollectingTransitionType();
+ return type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT;
+ }
return mAppTransition.containsTransitRequest(TRANSIT_OPEN)
|| mAppTransition.containsTransitRequest(TRANSIT_TO_FRONT);
}
@@ -5081,7 +5269,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
if (!mLocationInParentWindow.equals(x, y)) {
mLocationInParentWindow.set(x, y);
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(mDisplayId);
}
notifyLocationInParentDisplayChanged();
@@ -5412,17 +5600,33 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void onDisplayChanged() {
mDisplay.getRealSize(mTmpDisplaySize);
setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
+ final int lastDisplayState = mDisplayInfo.state;
updateDisplayInfo();
// The window policy is responsible for stopping activities on the default display.
final int displayId = mDisplay.getDisplayId();
+ final int displayState = mDisplayInfo.state;
if (displayId != DEFAULT_DISPLAY) {
- final int displayState = mDisplay.getState();
if (displayState == Display.STATE_OFF) {
mOffTokenAcquirer.acquire(mDisplayId);
} else if (displayState == Display.STATE_ON) {
mOffTokenAcquirer.release(mDisplayId);
}
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d state is now (%d), so update layer mirroring?",
+ mDisplayId, displayState);
+ if (lastDisplayState != displayState) {
+ // If state is on due to surface being added, then start layer mirroring.
+ // If state is off due to surface being removed, then stop layer mirroring.
+ updateMirroring();
+ }
+ }
+ // Dispatch pending Configuration to WindowContext if the associated display changes to
+ // un-suspended state from suspended.
+ if (isSuspendedState(lastDisplayState)
+ && !isSuspendedState(displayState) && displayState != STATE_UNKNOWN) {
+ mWmService.mWindowContextListenerController
+ .dispatchPendingConfigurationIfNeeded(mDisplayId);
}
mWmService.requestTraversal();
}
@@ -5603,6 +5807,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mWmService.mDisplayNotificationController.dispatchDisplayChanged(
this, getConfiguration());
+ if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
+ requestChangeTransitionIfNeeded(changes);
+ }
}
return changes;
}
@@ -5612,7 +5819,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final Configuration currOverrideConfig = getRequestedOverrideConfiguration();
final int currRotation = currOverrideConfig.windowConfiguration.getRotation();
final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation();
- if (currRotation != ROTATION_UNDEFINED && currRotation != overrideRotation) {
+ if (currRotation != ROTATION_UNDEFINED && overrideRotation != ROTATION_UNDEFINED
+ && currRotation != overrideRotation) {
applyRotationAndFinishFixedRotation(currRotation, overrideRotation);
}
mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
@@ -5623,6 +5831,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
}
+ @Override
+ void onResize() {
+ super.onResize();
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
+ mWmService.mAccessibilityController.onDisplaySizeChanged(this);
+ }
+ }
+
/**
* If the launching rotated activity ({@link #mFixedRotationLaunchingApp}) is null, it simply
* applies the rotation to display. Otherwise because the activity has shown as rotated, the
@@ -5703,6 +5919,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mRemoved = true;
+ if (mMirroredSurface != null) {
+ // Do not wait for the mirrored surface to be garbage collected, but clean up
+ // immediately.
+ mWmService.mTransactionFactory.get().remove(mMirroredSurface).apply();
+ mMirroredSurface = null;
+ }
+
// Only update focus/visibility for the last one because there may be many root tasks are
// reparented and the intermediate states are unnecessary.
if (lastReparentedRootTask != null) {
@@ -5865,6 +6088,200 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return true;
}
+ /**
+ * Sets if Display APIs should be sandboxed to the activity window bounds.
+ */
+ void setSandboxDisplayApis(boolean sandboxDisplayApis) {
+ mSandboxDisplayApis = sandboxDisplayApis;
+ }
+
+ /**
+ * Returns {@code true} is Display APIs should be sandboxed to the activity window bounds,
+ * {@code false} otherwise. Default to true, unless set for debugging purposes.
+ */
+ boolean sandboxDisplayApis() {
+ return mSandboxDisplayApis;
+ }
+
+ /**
+ * Start mirroring to this DisplayContent if it does not have its own content. Captures the
+ * content of a WindowContainer indicated by a WindowToken. If unable to start mirroring, falls
+ * back to original MediaProjection approach.
+ */
+ private void startMirrorIfNeeded() {
+ // Only mirror if this display does not have its own content, is not mirroring already,
+ // and if this display is on (it has a surface to write output to).
+ if (mLastHasContent || isCurrentlyMirroring() || mDisplay.getState() == Display.STATE_OFF) {
+ return;
+ }
+
+ // Given the WindowToken of the DisplayArea to mirror, retrieve the associated
+ // SurfaceControl.
+ IBinder tokenToMirror = mWmService.mDisplayManagerInternal.getWindowTokenClientToMirror(
+ mDisplayId);
+ if (tokenToMirror == null) {
+ // This DisplayContent instance is not involved in layer mirroring. If the display
+ // has been created for capturing, fall back to prior MediaProjection approach.
+ return;
+ }
+
+ final WindowContainer wc = mWmService.mWindowContextListenerController.getContainer(
+ tokenToMirror);
+ if (wc == null) {
+ // Un-set the window token to mirror for this VirtualDisplay, to fall back to the
+ // original MediaProjection approach.
+ mWmService.mDisplayManagerInternal.setWindowTokenClientToMirror(mDisplayId, null);
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to retrieve window container to start layer mirroring for display %d",
+ mDisplayId);
+ return;
+ }
+
+ Point surfaceSize = fetchSurfaceSizeIfPresent();
+ if (surfaceSize == null) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Unable to start layer mirroring for display %d since the surface is not "
+ + "available.",
+ mDisplayId);
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d has no content and is on, so start layer mirroring for state %d",
+ mDisplayId, mDisplay.getState());
+
+ // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
+ SurfaceControl sc = wc.getDisplayContent().getSurfaceControl();
+ mMirroredSurface = SurfaceControl.mirrorSurface(sc);
+ SurfaceControl.Transaction transaction = mWmService.mTransactionFactory.get()
+ // Set the mMirroredSurface's parent to the root SurfaceControl for this
+ // DisplayContent. This brings the new mirrored hierarchy under this DisplayContent,
+ // so SurfaceControl will write the layers of this hierarchy to the output surface
+ // provided by the app.
+ .reparent(mMirroredSurface, mSurfaceControl)
+ // Reparent the SurfaceControl of this DisplayContent to null, to prevent content
+ // being added to it. This ensures that no app launched explicitly on the
+ // VirtualDisplay will show up as part of the mirrored content.
+ .reparent(mWindowingLayer, null)
+ .reparent(mOverlayLayer, null);
+ // Retrieve the size of the DisplayArea to mirror.
+ updateMirroredSurface(transaction, wc.getDisplayContent().getBounds(), surfaceSize);
+ mTokenToMirror = tokenToMirror;
+
+ // No need to clean up. In SurfaceFlinger, parents hold references to their children. The
+ // mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
+ // holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
+ // when the VirtualDisplay is destroyed - which will clean up this DisplayContent.
+ }
+
+ /**
+ * Start mirroring if this DisplayContent no longer has content. Stop mirroring if it now
+ * has content or the display is not on.
+ */
+ private void updateMirroring() {
+ if (isCurrentlyMirroring() && (mLastHasContent
+ || mDisplay.getState() == Display.STATE_OFF)) {
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Display %d has content (%b) so disable layer mirroring", mDisplayId,
+ mLastHasContent);
+ // If the display is not on and it is a virtual display, then it no longer has an
+ // associated surface to write output to.
+ // If the display now has content, stop mirroring to it.
+ mWmService.mTransactionFactory.get()
+ // Remove the reference to mMirroredSurface, to clean up associated memory.
+ .remove(mMirroredSurface)
+ // Reparent the SurfaceControl of this DisplayContent back to mSurfaceControl,
+ // to allow content to be added to it. This allows this DisplayContent to stop
+ // mirroring and show content normally.
+ .reparent(mWindowingLayer, mSurfaceControl)
+ .reparent(mOverlayLayer, mSurfaceControl)
+ .apply();
+ // Stop mirroring by destroying the reference to the mirrored layer.
+ mMirroredSurface = null;
+ // Do not un-set the token, in case content is removed and mirroring should begin again.
+ } else {
+ // Display no longer has content, or now has a surface to write to, so try to start
+ // mirroring to it.
+ startMirrorIfNeeded();
+ }
+ }
+
+ /**
+ * Apply transformations to the mirrored surface to ensure the captured contents are scaled to
+ * fit and centred in the output surface.
+ *
+ * @param transaction the transaction to include transformations of mMirroredSurface
+ * to. Transaction is not applied before returning.
+ * @param displayAreaBounds bounds of the DisplayArea to mirror to the surface provided by
+ * the app.
+ * @param surfaceSize the default size of the surface to write the display area content to
+ */
+ @VisibleForTesting
+ void updateMirroredSurface(SurfaceControl.Transaction transaction,
+ Rect displayAreaBounds, Point surfaceSize) {
+ // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
+ // output surface.
+ float scaleX = surfaceSize.x / (float) displayAreaBounds.width();
+ float scaleY = surfaceSize.y / (float) displayAreaBounds.height();
+ float scale = Math.min(scaleX, scaleY);
+ int scaledWidth = Math.round(scale * (float) displayAreaBounds.width());
+ int scaledHeight = Math.round(scale * (float) displayAreaBounds.height());
+
+ // Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
+ // contents in the output surface.
+ int shiftedX = 0;
+ if (scaledWidth != surfaceSize.x) {
+ shiftedX = (surfaceSize.x - scaledWidth) / 2;
+ }
+ int shiftedY = 0;
+ if (scaledHeight != surfaceSize.y) {
+ shiftedY = (surfaceSize.y - scaledHeight) / 2;
+ }
+
+ transaction
+ // Crop the area to capture to exclude the 'extra' wallpaper that is used
+ // for parallax (b/189930234).
+ .setWindowCrop(mMirroredSurface, displayAreaBounds.width(),
+ displayAreaBounds.height())
+ // Scale the root mirror SurfaceControl, based upon the size difference between the
+ // source (DisplayArea to capture) and output (surface the app reads images from).
+ .setMatrix(mMirroredSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
+ // Position needs to be updated when the mirrored DisplayArea has changed, since
+ // the content will no longer be centered in the output surface.
+ .setPosition(mMirroredSurface, shiftedX /* x */, shiftedY /* y */)
+ .apply();
+ mLastMirroredDisplayAreaBounds = new Rect(displayAreaBounds);
+ }
+
+ /**
+ * Returns a non-null {@link Point} if the surface is present, or null otherwise
+ */
+ Point fetchSurfaceSizeIfPresent() {
+ // Retrieve the default size of the surface the app provided to
+ // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
+ // since it reads out buffers from the surface, and SurfaceFlinger is the producer since
+ // it writes the mirrored layers to the buffers.
+ Point surfaceSize = mWmService.mDisplayManagerInternal.getDisplaySurfaceDefaultSize(
+ mDisplayId);
+ if (surfaceSize == null) {
+ // Layer mirroring started with a null surface, so do not apply any transformations yet.
+ // State of virtual display will change to 'ON' when the surface is set.
+ // will get event DISPLAY_DEVICE_EVENT_CHANGED
+ ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
+ "Provided surface for layer mirroring on display %d is not present, so do not"
+ + " update the surface",
+ mDisplayId);
+ return null;
+ }
+ return surfaceSize;
+ }
+
+ /**
+ * Returns {@code true} if this DisplayContent is currently layer mirroring.
+ */
+ boolean isCurrentlyMirroring() {
+ return mTokenToMirror != null && mMirroredSurface != null;
+ }
+
/** The entry for proceeding to handle {@link #mFixedRotationLaunchingApp}. */
class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {
@@ -6009,7 +6426,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
class RemoteInsetsControlTarget implements InsetsControlTarget {
private final IDisplayWindowInsetsController mRemoteInsetsController;
- private final InsetsState mRequestedInsetsState = new InsetsState();
+ private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
mRemoteInsetsController = controller;
@@ -6071,15 +6488,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (type == ITYPE_IME) {
return getInsetsStateController().getImeSourceProvider().isImeShowing();
}
- return mRequestedInsetsState.getSourceOrDefaultVisibility(type);
+ return mRequestedVisibilities.getVisibility(type);
}
- void updateRequestedVisibility(InsetsState state) {
- for (int i = 0; i < InsetsState.SIZE; i++) {
- final InsetsSource source = state.peekSource(i);
- if (source == null) continue;
- mRequestedInsetsState.addSource(source);
- }
+ void setRequestedVisibilities(InsetsVisibilities requestedVisibilities) {
+ mRequestedVisibilities.set(requestedVisibilities);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 03b5478421cf..c1231da1ef93 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -16,13 +16,10 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
@@ -41,6 +38,7 @@ import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.ViewRootImpl.computeWindowBounds;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -126,8 +124,10 @@ import android.content.res.Resources;
import android.content.pm.ApplicationInfo;
import android.graphics.Insets;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.gui.DropInputMode;
import android.hardware.power.Boost;
import android.os.Handler;
import android.os.IBinder;
@@ -136,6 +136,7 @@ import android.os.Message;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.BoostFramework;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -147,6 +148,7 @@ import android.view.InsetsFlags;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
@@ -162,6 +164,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.SystemBarUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.util.function.TriConsumer;
@@ -178,6 +181,9 @@ import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.server.wm.InputMonitor.EventReceiverInputConsumer;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
import java.util.function.Consumer;
/**
@@ -331,17 +337,41 @@ public class DisplayPolicy {
private WindowState mSystemUiControllingWindow;
+ // Candidate window to determine the color of navigation bar. The window needs to be top
+ // fullscreen-app windows or dim layers that are intersecting with the window frame of status
+ // bar.
+ private WindowState mNavBarColorWindowCandidate;
+
+ // The window to determine opacity and background of translucent navigation bar. The window
+ // needs to be opaque.
+ private WindowState mNavBarBackgroundWindow;
+
+ /**
+ * Windows to determine the color of status bar. See {@link #mNavBarColorWindowCandidate} for
+ * the conditions of being candidate window.
+ */
+ private final ArrayList<WindowState> mStatusBarColorWindows = new ArrayList<>();
+
+ /**
+ * Windows to determine opacity and background of translucent status bar. The window needs to be
+ * opaque
+ */
+ private final ArrayList<WindowState> mStatusBarBackgroundWindows = new ArrayList<>();
+
+ private String mFocusedApp;
private int mLastDisableFlags;
private int mLastAppearance;
- private int mLastFullscreenAppearance;
- private int mLastDockedAppearance;
private int mLastBehavior;
- private final Rect mNonDockedRootTaskBounds = new Rect();
- private final Rect mDockedRootTaskBounds = new Rect();
- private final Rect mLastNonDockedRootTaskBounds = new Rect();
- private final Rect mLastDockedRootTaskBounds = new Rect();
+ private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private AppearanceRegion[] mLastStatusBarAppearanceRegions;
- // What we last reported to system UI about whether the focused window is fullscreen/immersive.
+ /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */
+ private final Rect mStatusBarColorCheckedBounds = new Rect();
+
+ /** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */
+ private final Rect mStatusBarBackgroundCheckedBounds = new Rect();
+
+ // What we last reported to input dispatcher about whether the focused window is fullscreen.
private boolean mLastFocusIsFullscreen = false;
// If nonzero, a panic gesture was performed at that time in uptime millis and is still pending.
@@ -351,19 +381,15 @@ public class DisplayPolicy {
private static final Rect sTmpRect = new Rect();
private static final Rect sTmpNavFrame = new Rect();
private static final Rect sTmpStatusFrame = new Rect();
+ private static final Rect sTmpDecorFrame = new Rect();
private static final Rect sTmpScreenDecorFrame = new Rect();
private static final Rect sTmpLastParentFrame = new Rect();
private static final Rect sTmpDisplayFrameBounds = new Rect();
private WindowState mTopFullscreenOpaqueWindowState;
- private WindowState mTopFullscreenOpaqueOrDimmingWindowState;
- private WindowState mTopDockedOpaqueWindowState;
- private WindowState mTopDockedOpaqueOrDimmingWindowState;
private boolean mTopIsFullscreen;
private boolean mForceStatusBar;
private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
- private boolean mForcingShowNavBar;
- private int mForcingShowNavBarLayer;
private boolean mForceShowSystemBars;
private boolean mShowingDream;
@@ -421,7 +447,7 @@ public class DisplayPolicy {
WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS)
? getStatusBar() : getNavigationBar();
if (targetBar != null) {
- requestTransientBars(targetBar);
+ requestTransientBars(targetBar, true /* isGestureOnSystemBar */);
}
}
break;
@@ -482,8 +508,10 @@ public class DisplayPolicy {
final int displayId = displayContent.getDisplayId();
- mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
- mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ mBarContentFrames.put(TYPE_STATUS_BAR, new Rect());
+ mBarContentFrames.put(TYPE_NAVIGATION_BAR, new Rect());
+ }
final Resources r = mContext.getResources();
mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
@@ -508,24 +536,25 @@ public class DisplayPolicy {
// TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context.
mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
new SystemGesturesPointerEventListener.Callbacks() {
+
@Override
public void onSwipeFromTop() {
synchronized (mLock) {
- if (mStatusBar != null) {
- requestTransientBars(mStatusBar);
- }
- checkAltBarSwipeForTransientBars(ALT_BAR_TOP);
+ final WindowState bar = mStatusBar != null
+ ? mStatusBar
+ : findAltBarMatchingPosition(ALT_BAR_TOP);
+ requestTransientBars(bar, true /* isGestureOnSystemBar */);
}
}
@Override
public void onSwipeFromBottom() {
synchronized (mLock) {
- if (mNavigationBar != null
- && mNavigationBarPosition == NAV_BAR_BOTTOM) {
- requestTransientBars(mNavigationBar);
- }
- checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM);
+ final WindowState bar = mNavigationBar != null
+ && mNavigationBarPosition == NAV_BAR_BOTTOM
+ ? mNavigationBar
+ : findAltBarMatchingPosition(ALT_BAR_BOTTOM);
+ requestTransientBars(bar, true /* isGestureOnSystemBar */);
}
}
@@ -535,13 +564,8 @@ public class DisplayPolicy {
synchronized (mLock) {
mDisplayContent.calculateSystemGestureExclusion(
excludedRegion, null /* outUnrestricted */);
- final boolean excluded =
- mSystemGestures.currentGestureStartedInRegion(excludedRegion);
- if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_RIGHT
- || !excluded && mNavigationBarAlwaysShowOnSideGesture)) {
- requestTransientBars(mNavigationBar);
- }
- checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT);
+ requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_RIGHT,
+ ALT_BAR_RIGHT);
}
excludedRegion.recycle();
}
@@ -552,17 +576,33 @@ public class DisplayPolicy {
synchronized (mLock) {
mDisplayContent.calculateSystemGestureExclusion(
excludedRegion, null /* outUnrestricted */);
- final boolean excluded =
- mSystemGestures.currentGestureStartedInRegion(excludedRegion);
- if (mNavigationBar != null && (mNavigationBarPosition == NAV_BAR_LEFT
- || !excluded && mNavigationBarAlwaysShowOnSideGesture)) {
- requestTransientBars(mNavigationBar);
- }
- checkAltBarSwipeForTransientBars(ALT_BAR_LEFT);
+ requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_LEFT,
+ ALT_BAR_LEFT);
}
excludedRegion.recycle();
}
+ private void requestTransientBarsForSideSwipe(Region excludedRegion,
+ int navBarSide, int altBarSide) {
+ final WindowState barMatchingSide = mNavigationBar != null
+ && mNavigationBarPosition == navBarSide
+ ? mNavigationBar
+ : findAltBarMatchingPosition(altBarSide);
+ final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture &&
+ !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+ if (barMatchingSide == null && !allowSideSwipe) {
+ return;
+ }
+
+ // Request transient bars on the matching bar, or any bar if we always allow
+ // side swipes to show the bars
+ final boolean isGestureOnSystemBar = barMatchingSide != null;
+ final WindowState bar = barMatchingSide != null
+ ? barMatchingSide
+ : findTransientNavOrAltBar();
+ requestTransientBars(bar, isGestureOnSystemBar);
+ }
+
@Override
public void onFling(int duration) {
if (mService.mPowerManagerInternal != null) {
@@ -747,7 +787,8 @@ public class DisplayPolicy {
}
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration,
long statusBarAnimationStartTime, long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -770,6 +811,7 @@ public class DisplayPolicy {
}
};
displayContent.mAppTransition.registerListenerLocked(mAppTransitionListener);
+ displayContent.mTransitionController.registerLegacyListener(mAppTransitionListener);
mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
mService.mVrModeEnabled);
@@ -809,19 +851,39 @@ public class DisplayPolicy {
mHandler.post(mGestureNavigationSettingsObserver::register);
}
- private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos) {
+ /**
+ * Returns the first non-null alt bar window matching the given position.
+ */
+ private WindowState findAltBarMatchingPosition(@WindowManagerPolicy.AltBarPosition int pos) {
if (mStatusBarAlt != null && mStatusBarAltPosition == pos) {
- requestTransientBars(mStatusBarAlt);
+ return mStatusBarAlt;
}
if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
- requestTransientBars(mNavigationBarAlt);
+ return mNavigationBarAlt;
}
if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
- requestTransientBars(mClimateBarAlt);
+ return mClimateBarAlt;
}
if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
- requestTransientBars(mExtraNavBarAlt);
+ return mExtraNavBarAlt;
+ }
+ return null;
+ }
+
+ /**
+ * Finds the first non-null nav bar to request transient for.
+ */
+ private WindowState findTransientNavOrAltBar() {
+ if (mNavigationBar != null) {
+ return mNavigationBar;
}
+ if (mNavigationBarAlt != null) {
+ return mNavigationBarAlt;
+ }
+ if (mExtraNavBarAlt != null) {
+ return mExtraNavBarAlt;
+ }
+ return null;
}
void systemReady() {
@@ -1024,15 +1086,6 @@ public class DisplayPolicy {
// letterboxed. Hence always let them extend under the cutout.
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
break;
- case TYPE_NOTIFICATION_SHADE:
- // If the Keyguard is in a hidden state (occluded by another window), we force to
- // remove the wallpaper and keyguard flag so that any change in-flight after setting
- // the keyguard as occluded wouldn't set these flags again.
- // See {@link #processKeyguardSetHiddenResultLw}.
- if (mService.mPolicy.isKeyguardOccluded()) {
- attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- break;
case TYPE_TOAST:
// While apps should use the dedicated toast APIs to add such windows
@@ -1085,6 +1138,20 @@ public class DisplayPolicy {
}
/**
+ * Add additional policy if needed to ensure the window or its children should not receive any
+ * input.
+ */
+ public void setDropInputModePolicy(WindowState win, LayoutParams attrs) {
+ if (attrs.type == TYPE_TOAST
+ && (attrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) == 0) {
+ // Toasts should not receive input. These windows should not have any children, so
+ // force this hierarchy of windows to drop all input.
+ mService.mTransactionFactory.get()
+ .setDropInputMode(win.getSurfaceControl(), DropInputMode.ALL).apply();
+ }
+ }
+
+ /**
* Check if a window can be added to the system.
*
* Currently enforces that two window types are singletons per display:
@@ -1228,15 +1295,14 @@ public class DisplayPolicy {
switch (attrs.type) {
case TYPE_NOTIFICATION_SHADE:
mNotificationShade = win;
- if (mDisplayContent.isDefaultDisplay) {
- mService.mPolicy.setKeyguardCandidateLw(win);
- }
break;
case TYPE_STATUS_BAR:
mStatusBar = win;
final TriConsumer<DisplayFrames, WindowState, Rect> frameProvider =
(displayFrames, windowState, rect) -> {
- rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+ }
};
final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider =
(displayFrames, windowState, rect) -> {
@@ -1259,18 +1325,22 @@ public class DisplayPolicy {
mNavigationBar = win;
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
(displayFrames, windowState, inOutFrame) -> {
-
- // In Gesture Nav, navigation bar frame is larger than frame to
- // calculate inset.
- if (navigationBarPosition(displayFrames.mDisplayWidth,
- displayFrames.mDisplayHeight,
- displayFrames.mRotation) == NAV_BAR_BOTTOM
- && !mNavButtonForcedVisible) {
- sTmpRect.set(inOutFrame);
- sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
- inOutFrame.top = sTmpRect.bottom
- - getNavigationBarHeight(displayFrames.mRotation,
- mDisplayContent.getConfiguration().uiMode);
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ inOutFrame.inset(windowState.getLayoutingAttrs(
+ displayFrames.mRotation).providedInternalInsets);
+ } else {
+ // In Gesture Nav, navigation bar frame is larger than frame to
+ // calculate inset.
+ if (navigationBarPosition(displayFrames.mDisplayWidth,
+ displayFrames.mDisplayHeight,
+ displayFrames.mRotation) == NAV_BAR_BOTTOM
+ && !mNavButtonForcedVisible) {
+ sTmpRect.set(inOutFrame);
+ sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+ inOutFrame.top = sTmpRect.bottom
+ - getNavigationBarHeight(displayFrames.mRotation,
+ mDisplayContent.getConfiguration().uiMode);
+ }
}
},
@@ -1313,6 +1383,12 @@ public class DisplayPolicy {
default:
if (attrs.providesInsetsTypes != null) {
for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+ final TriConsumer<DisplayFrames, WindowState, Rect> imeFrameProvider =
+ !attrs.providedInternalImeInsets.equals(Insets.NONE)
+ ? (displayFrames, windowState, inOutFrame) ->
+ inOutFrame.inset(windowState.getLayoutingAttrs(
+ displayFrames.mRotation).providedInternalImeInsets)
+ : null;
switch (insetsType) {
case ITYPE_STATUS_BAR:
mStatusBarAlt = win;
@@ -1331,7 +1407,15 @@ public class DisplayPolicy {
mExtraNavBarAltPosition = getAltBarPosition(attrs);
break;
}
- mDisplayContent.setInsetProvider(insetsType, win, null);
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ mDisplayContent.setInsetProvider(insetsType, win, null,
+ imeFrameProvider);
+ } else {
+ mDisplayContent.setInsetProvider(insetsType, win, (displayFrames,
+ windowState, inOutFrame) -> inOutFrame.inset(
+ windowState.getLayoutingAttrs(displayFrames.mRotation)
+ .providedInternalInsets), imeFrameProvider);
+ }
}
}
break;
@@ -1406,9 +1490,6 @@ public class DisplayPolicy {
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
} else if (mNotificationShade == win) {
mNotificationShade = null;
- if (mDisplayContent.isDefaultDisplay) {
- mService.mPolicy.setKeyguardCandidateLw(null);
- }
} else if (mClimateBarAlt == win) {
mClimateBarAlt = null;
mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
@@ -1422,8 +1503,22 @@ public class DisplayPolicy {
}
private int getStatusBarHeight(DisplayFrames displayFrames) {
- return Math.max(mStatusBarHeightForRotation[displayFrames.mRotation],
- displayFrames.mDisplayCutoutSafe.top);
+ int statusBarHeight;
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ if (mStatusBar != null) {
+ statusBarHeight = mStatusBar.getLayoutingAttrs(displayFrames.mRotation).height;
+ } else {
+ statusBarHeight = 0;
+ }
+ } else {
+ statusBarHeight = mStatusBarHeightForRotation[displayFrames.mRotation];
+ }
+ return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top);
+ }
+
+ @VisibleForTesting
+ int getStatusBarHeightForRotation(@Surface.Rotation int rotation) {
+ return SystemBarUtils.getStatusBarHeightForRotation(mUiContext, rotation);
}
WindowState getStatusBar() {
@@ -1558,7 +1653,7 @@ public class DisplayPolicy {
/**
* @return true if the system bars are forced to stay visible
*/
- public boolean areSystemBarsForcedShownLw(WindowState windowState) {
+ public boolean areSystemBarsForcedShownLw() {
return mForceShowSystemBars;
}
@@ -1593,13 +1688,30 @@ public class DisplayPolicy {
WindowFrames simulatedWindowFrames, SparseArray<Rect> contentFrames,
Consumer<Rect> layout) {
win.setSimulatedWindowFrames(simulatedWindowFrames);
+ final int requestedHeight = win.mRequestedHeight;
+ final int requestedWidth = win.mRequestedWidth;
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ // Without a full layout process, in order to layout the system bars correctly, we need
+ // to set the requested size and the initial display frames to the window.
+ WindowManager.LayoutParams params = win.getLayoutingAttrs(displayFrames.mRotation);
+ win.setRequestedSize(params.width, params.height);
+ sTmpDecorFrame.set(0, 0, displayFrames.mDisplayWidth, displayFrames.mDisplayHeight);
+ simulatedWindowFrames.setFrames(sTmpDecorFrame /* parentFrame */,
+ sTmpDecorFrame /* displayFrame */);
+ simulatedWindowFrames.mIsSimulatingDecorWindow = true;
+ }
final Rect contentFrame = new Rect();
try {
layout.accept(contentFrame);
} finally {
win.setSimulatedWindowFrames(null);
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ win.setRequestedSize(requestedWidth, requestedHeight);
+ }
+ }
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ contentFrames.put(win.mAttrs.type, contentFrame);
}
- contentFrames.put(win.mAttrs.type, contentFrame);
mDisplayContent.getInsetsStateController().computeSimulatedState(
win, displayFrames, simulatedWindowFrames);
}
@@ -1610,15 +1722,51 @@ public class DisplayPolicy {
* some temporal states, but doesn't change the window frames used to show on screen.
*/
void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) {
- final WindowFrames simulatedWindowFrames = new WindowFrames();
- if (mNavigationBar != null) {
- simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
- barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
- contentFrame));
- }
- if (mStatusBar != null) {
- simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
- barContentFrames, contentFrame -> layoutStatusBar(displayFrames, contentFrame));
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ final InsetsStateController insetsStateController =
+ mDisplayContent.getInsetsStateController();
+ for (int type = 0; type < InsetsState.SIZE; type++) {
+ final InsetsSourceProvider provider =
+ insetsStateController.peekSourceProvider(type);
+ if (provider == null || !provider.hasWindow()
+ || provider.mWin.getControllableInsetProvider() != provider) {
+ continue;
+ }
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(provider.mWin, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ provider.mWin, contentFrame));
+ }
+ } else {
+ if (mNavigationBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
+ barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
+ contentFrame));
+ }
+ if (mStatusBar != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> layoutStatusBar(displayFrames, contentFrame));
+ }
+ if (mExtraNavBarAlt != null) {
+ // There's no pre-defined behavior for the extra navigation bar, we need to use the
+ // new flexible insets logic anyway.
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mExtraNavBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mExtraNavBarAlt, contentFrame));
+ }
+ if (mClimateBarAlt != null) {
+ final WindowFrames simulatedWindowFrames = new WindowFrames();
+ simulateLayoutDecorWindow(mClimateBarAlt, displayFrames, simulatedWindowFrames,
+ barContentFrames,
+ contentFrame -> simulateLayoutForContentFrame(displayFrames,
+ mClimateBarAlt, contentFrame));
+ }
}
}
@@ -1637,13 +1785,11 @@ public class DisplayPolicy {
windowFrames.setFrames(sTmpStatusFrame /* parentFrame */,
sTmpStatusFrame /* displayFrame */);
// Let the status bar determine its size.
- mStatusBar.computeFrameAndUpdateSourceFrame();
+ mStatusBar.computeFrameAndUpdateSourceFrame(displayFrames);
// For layout, the status bar is always at the top with our fixed height.
int statusBarBottom = displayFrames.mUnrestricted.top
+ mStatusBarHeightForRotation[displayFrames.mRotation];
- // Make sure the status bar covers the entire cutout height
- statusBarBottom = Math.max(statusBarBottom, displayFrames.mDisplayCutoutSafe.top);
if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
// Make sure that the zone we're avoiding for the cutout is at least as tall as the
@@ -1688,18 +1834,18 @@ public class DisplayPolicy {
} else if (navBarPosition == NAV_BAR_RIGHT) {
// Landscape screen; nav bar goes to the right.
navigationFrame.left = Math.min(cutoutSafeUnrestricted.right, navigationFrame.right)
- - getNavigationBarWidth(rotation, uiMode);
+ - getNavigationBarWidth(rotation, uiMode, navBarPosition);
} else if (navBarPosition == NAV_BAR_LEFT) {
// Seascape screen; nav bar goes to the left.
navigationFrame.right = Math.max(cutoutSafeUnrestricted.left, navigationFrame.left)
- + getNavigationBarWidth(rotation, uiMode);
+ + getNavigationBarWidth(rotation, uiMode, navBarPosition);
}
// Compute the final frame.
final WindowFrames windowFrames = mNavigationBar.getLayoutingWindowFrames();
windowFrames.setFrames(navigationFrame /* parentFrame */,
navigationFrame /* displayFrame */);
- mNavigationBar.computeFrameAndUpdateSourceFrame();
+ mNavigationBar.computeFrameAndUpdateSourceFrame(displayFrames);
sTmpRect.set(windowFrames.mFrame);
sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
contentFrame.set(sTmpRect);
@@ -1708,6 +1854,16 @@ public class DisplayPolicy {
return navBarPosition;
}
+ private void simulateLayoutForContentFrame(DisplayFrames displayFrames, WindowState win,
+ Rect simulatedContentFrame) {
+ layoutWindowLw(win, null /* attached */, displayFrames);
+ final Rect contentFrame = sTmpRect;
+ contentFrame.set(win.getLayoutingWindowFrames().mFrame);
+ // Excluding the display cutout before set to the simulated content frame.
+ contentFrame.intersect(displayFrames.mDisplayCutoutSafe);
+ simulatedContentFrame.set(contentFrame);
+ }
+
private boolean canReceiveInput(WindowState win) {
boolean notFocusable =
(win.getAttrs().flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0;
@@ -1729,12 +1885,12 @@ public class DisplayPolicy {
* @param displayFrames The display frames.
*/
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
- if (win == mNavigationBar) {
+ if (win == mNavigationBar && !INSETS_LAYOUT_GENERALIZATION) {
mNavigationBarPosition = layoutNavigationBar(displayFrames,
mBarContentFrames.get(TYPE_NAVIGATION_BAR));
return;
}
- if ((win == mStatusBar && !canReceiveInput(win))) {
+ if ((win == mStatusBar && !canReceiveInput(win)) && !INSETS_LAYOUT_GENERALIZATION) {
layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
return;
}
@@ -1742,7 +1898,7 @@ public class DisplayPolicy {
// Skip layout of the window when in transition to pip mode.
return;
}
- final WindowManager.LayoutParams attrs = win.getAttrs();
+ final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
final int type = attrs.type;
final int fl = attrs.flags;
@@ -1750,7 +1906,7 @@ public class DisplayPolicy {
final int sim = attrs.softInputMode;
displayFrames = win.getDisplayFrames(displayFrames);
- final WindowFrames windowFrames = win.getWindowFrames();
+ final WindowFrames windowFrames = win.getLayoutingWindowFrames();
sTmpLastParentFrame.set(windowFrames.mParentFrame);
final Rect pf = windowFrames.mParentFrame;
@@ -1761,7 +1917,13 @@ public class DisplayPolicy {
final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
final InsetsState state = win.getInsetsState();
- computeWindowBounds(attrs, state, win.mToken.getBounds(), df);
+ if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) {
+ // Override the bounds in window token has many side effects. Directly use the display
+ // frame set for the simulated layout for this case.
+ computeWindowBounds(attrs, state, df, df);
+ } else {
+ computeWindowBounds(attrs, state, win.getBounds(), df);
+ }
if (attached == null) {
pf.set(df);
if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
@@ -1861,7 +2023,17 @@ public class DisplayPolicy {
windowFrames.setContentChanged(true);
}
- win.computeFrameAndUpdateSourceFrame();
+ win.computeFrameAndUpdateSourceFrame(displayFrames);
+ if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) {
+ if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
+ // Make sure that the zone we're avoiding for the cutout is at least as tall as the
+ // status bar; otherwise fullscreen apps will end up cutting halfway into the status
+ // bar.
+ displayFrames.mDisplayCutoutSafe.top = Math.max(
+ displayFrames.mDisplayCutoutSafe.top,
+ windowFrames.mFrame.bottom);
+ }
+ }
}
WindowState getTopFullscreenOpaqueWindow() {
@@ -1877,12 +2049,13 @@ public class DisplayPolicy {
*/
public void beginPostLayoutPolicyLw() {
mTopFullscreenOpaqueWindowState = null;
- mTopFullscreenOpaqueOrDimmingWindowState = null;
- mTopDockedOpaqueWindowState = null;
- mTopDockedOpaqueOrDimmingWindowState = null;
+ mNavBarColorWindowCandidate = null;
+ mNavBarBackgroundWindow = null;
+ mStatusBarColorWindows.clear();
+ mStatusBarBackgroundWindows.clear();
+ mStatusBarColorCheckedBounds.setEmpty();
+ mStatusBarBackgroundCheckedBounds.setEmpty();
mForceStatusBar = false;
- mForcingShowNavBar = false;
- mForcingShowNavBarLayer = -1;
mAllowLockscreenWhenOn = false;
mShowingDream = false;
@@ -1901,20 +2074,22 @@ public class DisplayPolicy {
final boolean affectsSystemUi = win.canAffectSystemUiFlags();
if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
applyKeyguardPolicy(win, imeTarget);
- final int fl = attrs.flags;
- if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi
- && attrs.type == TYPE_INPUT_METHOD) {
- mForcingShowNavBar = true;
- mForcingShowNavBarLayer = win.getSurfaceLayer();
+
+ // Check if the freeform window overlaps with the navigation bar area.
+ final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win);
+ if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar
+ && win.inFreeformWindowingMode()) {
+ mIsFreeformWindowOverlappingWithNavBar = true;
+ }
+
+ if (!affectsSystemUi) {
+ return;
}
boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
&& attrs.type < FIRST_SYSTEM_WINDOW;
- final int windowingMode = win.getWindowingMode();
- final boolean inFullScreenOrSplitScreenSecondaryWindowingMode =
- windowingMode == WINDOWING_MODE_FULLSCREEN
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
- if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) {
+ if (mTopFullscreenOpaqueWindowState == null) {
+ final int fl = attrs.flags;
if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
mForceStatusBar = true;
}
@@ -1927,68 +2102,60 @@ public class DisplayPolicy {
}
}
- // For app windows that are not attached, we decide if all windows in the app they
- // represent should be hidden or if we should hide the lockscreen. For attached app
- // windows we defer the decision to the window it is attached to.
- if (appWindow && attached == null) {
- if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
- if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
- mTopFullscreenOpaqueWindowState = win;
- if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
- mTopFullscreenOpaqueOrDimmingWindowState = win;
- }
- if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
- mAllowLockscreenWhenOn = true;
- }
- }
+ if (appWindow && attached == null && attrs.isFullscreen()
+ && (fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
+ mAllowLockscreenWhenOn = true;
}
}
- // Voice interaction overrides both top fullscreen and top docked.
- if (affectsSystemUi && attrs.type == TYPE_VOICE_INTERACTION && attrs.isFullscreen()) {
+ // Check the windows that overlap with system bars to determine system bars' appearance.
+ if ((appWindow && attached == null && attrs.isFullscreen())
+ || attrs.type == TYPE_VOICE_INTERACTION) {
+ // Record the top-fullscreen-app-window which will be used to determine system UI
+ // controlling window.
if (mTopFullscreenOpaqueWindowState == null) {
mTopFullscreenOpaqueWindowState = win;
- if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
- mTopFullscreenOpaqueOrDimmingWindowState = win;
- }
}
- if (mTopDockedOpaqueWindowState == null) {
- mTopDockedOpaqueWindowState = win;
- if (mTopDockedOpaqueOrDimmingWindowState == null) {
- mTopDockedOpaqueOrDimmingWindowState = win;
+
+ // Cache app windows that is overlapping with the status bar to determine appearance
+ // of status bar.
+ if (mStatusBar != null
+ && sTmpRect.setIntersect(win.getFrame(), mStatusBar.getFrame())
+ && !mStatusBarBackgroundCheckedBounds.contains(sTmpRect)) {
+ mStatusBarBackgroundWindows.add(win);
+ mStatusBarBackgroundCheckedBounds.union(sTmpRect);
+ if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) {
+ mStatusBarColorWindows.add(win);
+ mStatusBarColorCheckedBounds.union(sTmpRect);
}
}
- }
-
- // Keep track of the window if it's dimming but not necessarily fullscreen.
- if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi
- && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
- mTopFullscreenOpaqueOrDimmingWindowState = win;
- }
- // We need to keep track of the top "fullscreen" opaque window for the docked root task
- // separately, because both the "real fullscreen" opaque window and the one for the docked
- // root task can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
- if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null
- && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mTopDockedOpaqueWindowState = win;
- if (mTopDockedOpaqueOrDimmingWindowState == null) {
- mTopDockedOpaqueOrDimmingWindowState = win;
+ // Cache app window that overlaps with the navigation bar area to determine opacity
+ // and appearance of the navigation bar. We only need to cache one window because
+ // there should be only one overlapping window if it's not in gesture navigation
+ // mode; if it's in gesture navigation mode, the navigation bar will be
+ // NAV_BAR_FORCE_TRANSPARENT and its appearance won't be decided by overlapping
+ // windows.
+ if (isOverlappingWithNavBar) {
+ if (mNavBarColorWindowCandidate == null) {
+ mNavBarColorWindowCandidate = win;
+ }
+ if (mNavBarBackgroundWindow == null) {
+ mNavBarBackgroundWindow = win;
+ }
+ }
+ } else if (win.isDimming()) {
+ // For dimming window whose host bounds is overlapping with system bars, it can be
+ // used to determine colors but not opacity of system bars.
+ if (mStatusBar != null
+ && sTmpRect.setIntersect(win.getBounds(), mStatusBar.getFrame())
+ && !mStatusBarColorCheckedBounds.contains(sTmpRect)) {
+ mStatusBarColorWindows.add(win);
+ mStatusBarColorCheckedBounds.union(sTmpRect);
+ }
+ if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
+ mNavBarColorWindowCandidate = win;
}
- }
-
- // Check if the freeform window overlaps with the navigation bar area.
- final WindowState navBarWin = hasNavigationBar() ? mNavigationBar : null;
- if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode()
- && isOverlappingWithNavBar(win, navBarWin)) {
- mIsFreeformWindowOverlappingWithNavBar = true;
- }
-
- // Also keep track of any windows that are dimming but not necessarily fullscreen in the
- // docked root task.
- if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming()
- && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- mTopDockedOpaqueOrDimmingWindowState = win;
}
}
@@ -2052,11 +2219,7 @@ public class DisplayPolicy {
mTopIsFullscreen = topIsFullscreen;
}
- if (updateSystemUiVisibilityLw()) {
- // If the navigation bar has been hidden or shown, we need to do another
- // layout pass to update that window.
- changes |= FINISH_LAYOUT_REDO_LAYOUT;
- }
+ updateSystemBarAttributes();
if (mShowingDream != mLastShowingDream) {
mLastShowingDream = mShowingDream;
@@ -2159,10 +2322,11 @@ public class DisplayPolicy {
if (hasStatusBar()) {
mStatusBarHeightForRotation[portraitRotation] =
mStatusBarHeightForRotation[upsideDownRotation] =
- res.getDimensionPixelSize(R.dimen.status_bar_height_portrait);
+ getStatusBarHeightForRotation(portraitRotation);
mStatusBarHeightForRotation[landscapeRotation] =
- mStatusBarHeightForRotation[seascapeRotation] =
- res.getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+ getStatusBarHeightForRotation(landscapeRotation);
+ mStatusBarHeightForRotation[seascapeRotation] =
+ getStatusBarHeightForRotation(seascapeRotation);
mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
R.dimen.display_cutout_touchable_region_size);
} else {
@@ -2294,18 +2458,65 @@ public class DisplayPolicy {
return mUiContext;
}
- private int getNavigationBarWidth(int rotation, int uiMode) {
- if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
- return mNavigationBarWidthForRotationInCarMode[rotation];
+ private int getNavigationBarWidth(int rotation, int uiMode, int position) {
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ if (mNavigationBar == null) {
+ return 0;
+ }
+ LayoutParams lp = mNavigationBar.mAttrs;
+ if (lp.paramsForRotation != null
+ && lp.paramsForRotation.length == 4
+ && lp.paramsForRotation[rotation] != null) {
+ lp = lp.paramsForRotation[rotation];
+ }
+ if (position == NAV_BAR_LEFT) {
+ if (lp.width > lp.providedInternalInsets.right) {
+ return lp.width - lp.providedInternalInsets.right;
+ } else {
+ return 0;
+ }
+ } else if (position == NAV_BAR_RIGHT) {
+ if (lp.width > lp.providedInternalInsets.left) {
+ return lp.width - lp.providedInternalInsets.left;
+ } else {
+ return 0;
+ }
+ }
+ return lp.width;
} else {
- return mNavigationBarWidthForRotationDefault[rotation];
+ if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+ return mNavigationBarWidthForRotationInCarMode[rotation];
+ } else {
+ return mNavigationBarWidthForRotationDefault[rotation];
+ }
+ }
+ }
+
+ private int getAltBarWidth(@InternalInsetsType int insetsType) {
+ final InsetsSource source = mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .peekSource(insetsType);
+ if (source == null) {
+ return 0;
}
+ return source.getFrame().width();
+ }
+
+ private int getAltBarHeight(@InternalInsetsType int insetsType) {
+ final InsetsSource source = mDisplayContent.getInsetsStateController().getRawInsetsState()
+ .peekSource(insetsType);
+ if (source == null) {
+ return 0;
+ }
+ return source.getFrame().height();
}
void notifyDisplayReady() {
mHandler.post(() -> {
final int displayId = getDisplayId();
- getStatusBarManagerInternal().onDisplayReady(displayId);
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.onDisplayReady(displayId);
+ }
final WallpaperManagerInternal wpMgr = LocalServices
.getService(WallpaperManagerInternal.class);
if (wpMgr != null) {
@@ -2314,31 +2525,22 @@ public class DisplayPolicy {
});
}
- /**
- * Return the display width available after excluding any screen
- * decorations that could never be removed in Honeycomb. That is, system bar or
- * button bar.
- */
- public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
- DisplayCutout displayCutout) {
- int width = fullWidth;
- if (hasNavigationBar()) {
- final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
- if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
- width -= getNavigationBarWidth(rotation, uiMode);
- }
- }
- if (displayCutout != null) {
- width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
- }
- return width;
- }
-
private int getNavigationBarHeight(int rotation, int uiMode) {
- if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
- return mNavigationBarHeightForRotationInCarMode[rotation];
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ if (mNavigationBar == null) {
+ return 0;
+ }
+ LayoutParams lp = mNavigationBar.getLayoutingAttrs(rotation);
+ if (lp.height < lp.providedInternalInsets.top) {
+ return 0;
+ }
+ return lp.height - lp.providedInternalInsets.top;
} else {
- return mNavigationBarHeightForRotationDefault[rotation];
+ if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+ return mNavigationBarHeightForRotationInCarMode[rotation];
+ } else {
+ return mNavigationBarHeightForRotationDefault[rotation];
+ }
}
}
@@ -2355,51 +2557,74 @@ public class DisplayPolicy {
* @return navigation bar frame height
*/
private int getNavigationBarFrameHeight(int rotation, int uiMode) {
- if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
- return mNavigationBarHeightForRotationInCarMode[rotation];
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ if (mNavigationBar == null) {
+ return 0;
+ }
+ return mNavigationBar.mAttrs.height;
} else {
- return mNavigationBarFrameHeightForRotationDefault[rotation];
+ if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+ return mNavigationBarHeightForRotationInCarMode[rotation];
+ } else {
+ return mNavigationBarFrameHeightForRotationDefault[rotation];
+ }
}
}
/**
- * Return the display height available after excluding any screen
+ * Return the display size available after excluding any screen
* decorations that could never be removed in Honeycomb. That is, system bar or
* button bar.
*/
- public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
+ Point getNonDecorDisplaySize(int fullWidth, int fullHeight, int rotation, int uiMode,
DisplayCutout displayCutout) {
+ int width = fullWidth;
int height = fullHeight;
+ int navBarReducedHeight = 0;
+ int navBarReducedWidth = 0;
+ final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
if (hasNavigationBar()) {
- final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
if (navBarPosition == NAV_BAR_BOTTOM) {
- height -= getNavigationBarHeight(rotation, uiMode);
+ navBarReducedHeight = getNavigationBarHeight(rotation, uiMode);
+ } else if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
+ navBarReducedWidth = getNavigationBarWidth(rotation, uiMode, navBarPosition);
+ }
+ }
+ if (mExtraNavBarAlt != null) {
+ final LayoutParams altBarParams = mExtraNavBarAlt.getLayoutingAttrs(rotation);
+ final int altBarPosition = getAltBarPosition(altBarParams);
+ if (altBarPosition == ALT_BAR_BOTTOM || altBarPosition == ALT_BAR_TOP) {
+ if (altBarPosition == navBarPosition) {
+ navBarReducedHeight = Math.max(navBarReducedHeight,
+ getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR));
+ } else {
+ navBarReducedHeight += getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR);
+ }
+ } else if (altBarPosition == ALT_BAR_LEFT || altBarPosition == ALT_BAR_RIGHT) {
+ if (altBarPosition == navBarPosition) {
+ navBarReducedWidth = Math.max(navBarReducedWidth,
+ getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR));
+ } else {
+ navBarReducedWidth += getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR);
+ }
}
}
+ height -= navBarReducedHeight;
+ width -= navBarReducedWidth;
if (displayCutout != null) {
height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom();
+ width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
}
- return height;
- }
-
- /**
- * Return the available screen width that we should report for the
- * configuration. This must be no larger than
- * {@link #getNonDecorDisplayWidth(int, int, int, int, DisplayCutout)}; it may be smaller
- * than that to account for more transient decoration like a status bar.
- */
- public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
- DisplayCutout displayCutout) {
- return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout);
+ return new Point(width, height);
}
/**
- * Return the available screen height that we should report for the
+ * Return the available screen size that we should report for the
* configuration. This must be no larger than
- * {@link #getNonDecorDisplayHeight(int, int, int, int, DisplayCutout)}; it may be smaller
+ * {@link #getNonDecorDisplaySize(int, int, int, int, DisplayCutout)}; it may be smaller
* than that to account for more transient decoration like a status bar.
*/
- public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
+ Point getConfigDisplaySize(int fullWidth, int fullHeight, int rotation, int uiMode,
DisplayCutout displayCutout) {
// There is a separate status bar at the top of the display. We don't count that as part
// of the fixed decor, since it can hide; however, for purposes of configurations,
@@ -2411,8 +2636,9 @@ public class DisplayPolicy {
// bar height.
statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop());
}
- return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation, uiMode, displayCutout)
- - statusBarHeight;
+ final Point nonDecorSize = getNonDecorDisplaySize(fullWidth, fullHeight, rotation,
+ uiMode, displayCutout);
+ return new Point(nonDecorSize.x, nonDecorSize.y - statusBarHeight);
}
/**
@@ -2423,7 +2649,7 @@ public class DisplayPolicy {
*/
float getWindowCornerRadius() {
return mDisplayContent.getDisplay().getType() == TYPE_INTERNAL
- ? ScreenDecorationsUtils.getWindowCornerRadius(mContext.getResources()) : 0f;
+ ? ScreenDecorationsUtils.getWindowCornerRadius(mContext) : 0f;
}
boolean isShowingDreamLw() {
@@ -2460,7 +2686,7 @@ public class DisplayPolicy {
/**
* Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
- * bar or button bar. See {@link #getNonDecorDisplayWidth}.
+ * bar or button bar. See {@link #getNonDecorDisplaySize}.
*
* @param displayRotation the current display rotation
* @param displayWidth the current display width
@@ -2472,16 +2698,34 @@ public class DisplayPolicy {
DisplayCutout displayCutout, Rect outInsets) {
outInsets.setEmpty();
- // Only navigation bar
+ // Only navigation bar and extra navigation bar
if (hasNavigationBar()) {
final int uiMode = mService.mPolicy.getUiMode();
int position = navigationBarPosition(displayWidth, displayHeight, displayRotation);
if (position == NAV_BAR_BOTTOM) {
outInsets.bottom = getNavigationBarHeight(displayRotation, uiMode);
} else if (position == NAV_BAR_RIGHT) {
- outInsets.right = getNavigationBarWidth(displayRotation, uiMode);
+ outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position);
} else if (position == NAV_BAR_LEFT) {
- outInsets.left = getNavigationBarWidth(displayRotation, uiMode);
+ outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position);
+ }
+ }
+ if (mExtraNavBarAlt != null) {
+ final LayoutParams extraNavLayoutParams =
+ mExtraNavBarAlt.getLayoutingAttrs(displayRotation);
+ final int position = getAltBarPosition(extraNavLayoutParams);
+ if (position == ALT_BAR_BOTTOM) {
+ outInsets.bottom = Math.max(outInsets.bottom,
+ getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR));
+ } else if (position == ALT_BAR_RIGHT) {
+ outInsets.right = Math.max(outInsets.right,
+ getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR));
+ } else if (position == ALT_BAR_LEFT) {
+ outInsets.left = Math.max(outInsets.left,
+ getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR));
+ } else if (position == ALT_BAR_TOP) {
+ outInsets.top = Math.max(outInsets.top,
+ getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR));
}
}
@@ -2507,6 +2751,17 @@ public class DisplayPolicy {
@NavigationBarPosition
int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
+ if (INSETS_LAYOUT_GENERALIZATION && mNavigationBar != null) {
+ final int gravity = mNavigationBar.getLayoutingAttrs(displayRotation).gravity;
+ switch (gravity) {
+ case Gravity.LEFT:
+ return NAV_BAR_LEFT;
+ case Gravity.RIGHT:
+ return NAV_BAR_RIGHT;
+ default:
+ return NAV_BAR_BOTTOM;
+ }
+ }
if (navigationBarCanMove() && displayWidth > displayHeight) {
if (displayRotation == Surface.ROTATION_270) {
return NAV_BAR_LEFT;
@@ -2541,22 +2796,17 @@ public class DisplayPolicy {
/**
* A new window has been focused.
*/
- public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
+ public void focusChangedLw(WindowState lastFocus, WindowState newFocus) {
mFocusedWindow = newFocus;
mLastFocusedWindow = lastFocus;
if (mDisplayContent.isDefaultDisplay) {
mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus);
}
- if (updateSystemUiVisibilityLw()) {
- // If the navigation bar has been hidden or shown, we need to do another
- // layout pass to update that window.
- return FINISH_LAYOUT_REDO_LAYOUT;
- }
- return 0;
+ updateSystemBarAttributes();
}
- private void requestTransientBars(WindowState swipeTarget) {
- if (!mService.mPolicy.isUserSetupComplete()) {
+ private void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) {
+ if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) {
// Swipe-up for navigation bar is disabled during setup
return;
}
@@ -2592,7 +2842,8 @@ public class DisplayPolicy {
if (controlTarget.canShowTransient()) {
// Show transient bars if they are hidden; restore position if they are visible.
- mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE);
+ mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE,
+ isGestureOnSystemBar);
controlTarget.showInsets(restorePositionTypes, false);
} else {
// Restore visibilities and positions of system bars.
@@ -2628,21 +2879,23 @@ public class DisplayPolicy {
return mDisplayContent.getInsetsPolicy();
}
- void resetSystemUiVisibilityLw() {
+ void resetSystemBarAttributes() {
mLastDisableFlags = 0;
- updateSystemUiVisibilityLw();
+ updateSystemBarAttributes();
}
- /**
- * @return {@code true} if the update may affect the layout.
- */
- boolean updateSystemUiVisibilityLw() {
+ void updateSystemBarAttributes() {
+ WindowState winCandidate = mFocusedWindow;
+ if (winCandidate == null && mTopFullscreenOpaqueWindowState != null
+ && (mTopFullscreenOpaqueWindowState.mAttrs.flags
+ & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) {
+ // Only focusable window can take system bar control.
+ winCandidate = mTopFullscreenOpaqueWindowState;
+ }
// If there is no window focused, there will be nobody to handle the events
// anyway, so just hang on in whatever state we're in until things settle down.
- WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
- : mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
- return false;
+ return;
}
// The immersive mode confirmation should never affect the system bar visibility, otherwise
@@ -2658,100 +2911,85 @@ public class DisplayPolicy {
: lastFocusCanReceiveKeys ? mLastFocusedWindow
: mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
- return false;
+ return;
}
}
final WindowState win = winCandidate;
mSystemUiControllingWindow = win;
- mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
-
- final boolean inSplitScreen =
- mService.mRoot.getDefaultTaskDisplayArea().isSplitScreenModeActivated();
- if (inSplitScreen) {
- mService.getRootTaskBounds(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
- mDockedRootTaskBounds);
- } else {
- mDockedRootTaskBounds.setEmpty();
- }
- mService.getRootTaskBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- : WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_UNDEFINED, mNonDockedRootTaskBounds);
- final int fullscreenAppearance = getStatusBarAppearance(mTopFullscreenOpaqueWindowState,
- mTopFullscreenOpaqueOrDimmingWindowState);
- final int dockedAppearance = getStatusBarAppearance(mTopDockedOpaqueWindowState,
- mTopDockedOpaqueOrDimmingWindowState);
+ final int displayId = getDisplayId();
final int disableFlags = win.getDisableFlags();
final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
- final WindowState navColorWin = chooseNavigationColorWindowLw(
- mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+ final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
final boolean isNavbarColorManagedByIme =
navColorWin != null && navColorWin == mDisplayContent.mInputMethodWindow;
- final int appearance = updateLightNavigationBarLw(
- win.mAttrs.insetsFlags.appearance, mTopFullscreenOpaqueWindowState,
- mTopFullscreenOpaqueOrDimmingWindowState,
- mDisplayContent.mInputMethodWindow, navColorWin) | opaqueAppearance;
+ final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance,
+ navColorWin) | opaqueAppearance;
final int behavior = win.mAttrs.insetsFlags.behavior;
+ final String focusedApp = win.mAttrs.packageName;
final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
|| !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
- if (mLastDisableFlags == disableFlags
- && mLastAppearance == appearance
- && mLastFullscreenAppearance == fullscreenAppearance
- && mLastDockedAppearance == dockedAppearance
+ final AppearanceRegion[] appearanceRegions =
+ new AppearanceRegion[mStatusBarColorWindows.size()];
+ for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
+ final WindowState windowState = mStatusBarColorWindows.get(i);
+ appearanceRegions[i] = new AppearanceRegion(
+ getStatusBarAppearance(windowState, windowState),
+ new Rect(windowState.getFrame()));
+ }
+ if (mLastDisableFlags != disableFlags) {
+ mLastDisableFlags = disableFlags;
+ final String cause = win.toString();
+ callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
+ cause));
+ }
+ if (mLastAppearance == appearance
&& mLastBehavior == behavior
+ && mRequestedVisibilities.equals(win.getRequestedVisibilities())
+ && Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
- && mLastNonDockedRootTaskBounds.equals(mNonDockedRootTaskBounds)
- && mLastDockedRootTaskBounds.equals(mDockedRootTaskBounds)) {
- return false;
+ && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) {
+ return;
}
if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen
&& ((mLastAppearance ^ appearance) & APPEARANCE_LOW_PROFILE_BARS) != 0) {
mService.mInputManager.setSystemUiLightsOut(
isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
}
- mLastDisableFlags = disableFlags;
+ final InsetsVisibilities requestedVisibilities =
+ new InsetsVisibilities(win.getRequestedVisibilities());
mLastAppearance = appearance;
- mLastFullscreenAppearance = fullscreenAppearance;
- mLastDockedAppearance = dockedAppearance;
mLastBehavior = behavior;
+ mRequestedVisibilities = requestedVisibilities;
+ mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
- mLastNonDockedRootTaskBounds.set(mNonDockedRootTaskBounds);
- mLastDockedRootTaskBounds.set(mDockedRootTaskBounds);
- final Rect fullscreenRootTaskBounds = new Rect(mNonDockedRootTaskBounds);
- final Rect dockedRootTaskBounds = new Rect(mDockedRootTaskBounds);
- final AppearanceRegion[] appearanceRegions = inSplitScreen
- ? new AppearanceRegion[]{
- new AppearanceRegion(fullscreenAppearance, fullscreenRootTaskBounds),
- new AppearanceRegion(dockedAppearance, dockedRootTaskBounds)}
- : new AppearanceRegion[]{
- new AppearanceRegion(fullscreenAppearance, fullscreenRootTaskBounds)};
- String cause = win.toString();
- mHandler.post(() -> {
- StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
- if (statusBar != null) {
- final int displayId = getDisplayId();
- statusBar.setDisableFlags(displayId, disableFlags, cause);
- statusBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- isNavbarColorManagedByIme, behavior, isFullscreen);
-
- }
- });
- return true;
+ mLastStatusBarAppearanceRegions = appearanceRegions;
+ callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
+ appearance, appearanceRegions, isNavbarColorManagedByIme, behavior,
+ requestedVisibilities, focusedApp));
}
private int getStatusBarAppearance(WindowState opaque, WindowState opaqueOrDimming) {
final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded();
final WindowState colorWin = onKeyguard ? mNotificationShade : opaqueOrDimming;
- return isLightBarAllowed(colorWin, ITYPE_STATUS_BAR) && (colorWin == opaque || onKeyguard)
+ return isLightBarAllowed(colorWin, Type.statusBars()) && (colorWin == opaque || onKeyguard)
? (colorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS)
: 0;
}
+ private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
+ mHandler.post(() -> {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ consumer.accept(statusBar);
+ }
+ });
+ }
+
@VisibleForTesting
@Nullable
- static WindowState chooseNavigationColorWindowLw(WindowState opaque,
- WindowState opaqueOrDimming, WindowState imeWindow,
+ static WindowState chooseNavigationColorWindowLw(WindowState candidate, WindowState imeWindow,
@NavigationBarPosition int navBarPosition) {
// If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME
// window can be navigation color window.
@@ -2760,71 +2998,58 @@ public class DisplayPolicy {
&& navBarPosition == NAV_BAR_BOTTOM
&& (imeWindow.mAttrs.flags
& WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
-
- if (opaque != null && opaqueOrDimming == opaque) {
- // If the top fullscreen-or-dimming window is also the top fullscreen, respect it
- // unless IME window is also eligible, since currently the IME window is always show
- // above the opaque fullscreen app window, regardless of the IME target window.
- // TODO(b/31559891): Maybe we need to revisit this condition once b/31559891 is fixed.
- return imeWindowCanNavColorWindow ? imeWindow : opaque;
- }
-
- if (opaqueOrDimming == null || !opaqueOrDimming.isDimming()) {
- // No dimming window is involved. Determine the result only with the IME window.
- return imeWindowCanNavColorWindow ? imeWindow : null;
- }
-
if (!imeWindowCanNavColorWindow) {
- // No IME window is involved. Determine the result only with opaqueOrDimming.
- return opaqueOrDimming;
+ // No IME window is involved. Determine the result only with candidate window.
+ return candidate;
}
- // The IME window and the dimming window are competing. Check if the dimming window can be
- // IME target or not.
- if (LayoutParams.mayUseInputMethod(opaqueOrDimming.mAttrs.flags)) {
- // The IME window is above the dimming window.
- return imeWindow;
- } else {
- // The dimming window is above the IME window.
- return opaqueOrDimming;
+ if (candidate != null && candidate.isDimming()) {
+ // The IME window and the dimming window are competing. Check if the dimming window can
+ // be IME target or not.
+ if (LayoutParams.mayUseInputMethod(candidate.mAttrs.flags)) {
+ // The IME window is above the dimming window.
+ return imeWindow;
+ } else {
+ // The dimming window is above the IME window.
+ return candidate;
+ }
}
+
+ return imeWindow;
}
@VisibleForTesting
- int updateLightNavigationBarLw(int appearance, WindowState opaque,
- WindowState opaqueOrDimming, WindowState imeWindow, WindowState navColorWin) {
-
- if (navColorWin != null) {
- if (navColorWin == imeWindow || navColorWin == opaque) {
- // Respect the light flag.
- appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
- appearance |= navColorWin.mAttrs.insetsFlags.appearance
- & APPEARANCE_LIGHT_NAVIGATION_BARS;
- } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) {
- // Clear the light flag for dimming window.
- appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
- }
- }
- if (!isLightBarAllowed(navColorWin, ITYPE_NAVIGATION_BAR)) {
+ int updateLightNavigationBarLw(int appearance, WindowState navColorWin) {
+ if (navColorWin == null || !isLightBarAllowed(navColorWin, Type.navigationBars())) {
+ // Clear the light flag while not allowed.
appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
+ return appearance;
}
+
+ // Respect the light flag of navigation color window.
+ appearance &= ~APPEARANCE_LIGHT_NAVIGATION_BARS;
+ appearance |= navColorWin.mAttrs.insetsFlags.appearance
+ & APPEARANCE_LIGHT_NAVIGATION_BARS;
return appearance;
}
private int updateSystemBarsLw(WindowState win, int disableFlags) {
- final boolean dockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
- .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- final boolean resizing = mDisplayContent.getDockedDividerController().isResizing();
-
- // We need to force system bars when the docked root task is visible, when the freeform
- // root task is focused but also when we are resizing for the transitions when docked
- // root task visibility changes.
- mForceShowSystemBars = dockedRootTaskVisible || win.inFreeformWindowingMode() || resizing;
+ final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
+ final boolean multiWindowTaskVisible =
+ defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+ || defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW);
+ final boolean freeformRootTaskVisible =
+ defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM);
+
+ // We need to force showing system bars when the multi-window or freeform root task is
+ // visible.
+ mForceShowSystemBars = multiWindowTaskVisible || freeformRootTaskVisible;
+ mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
int appearance = APPEARANCE_OPAQUE_NAVIGATION_BARS | APPEARANCE_OPAQUE_STATUS_BARS;
-
appearance = configureStatusBarOpacity(appearance);
- appearance = configureNavBarOpacity(appearance, dockedRootTaskVisible, resizing);
+ appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible,
+ freeformRootTaskVisible);
final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
final long now = SystemClock.uptimeMillis();
@@ -2840,7 +3065,8 @@ public class DisplayPolicy {
// we're no longer on the Keyguard and the screen is ready. We can now request the bars.
mPendingPanicGestureUptime = 0;
if (!isNavBarEmpty(disableFlags)) {
- mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC);
+ mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_PANIC,
+ true /* isGestureOnSystemBar */);
}
}
@@ -2860,17 +3086,33 @@ public class DisplayPolicy {
return appearance;
}
- private boolean isLightBarAllowed(WindowState win, @InternalInsetsType int type) {
+ private static boolean isLightBarAllowed(WindowState win, @InsetsType int type) {
if (win == null) {
return false;
}
- final InsetsSource source = win.getInsetsState().peekSource(type);
- return source != null && Rect.intersects(win.getFrame(), source.getFrame());
+ return intersectsAnyInsets(win.getFrame(), win.getInsetsState(), type);
}
private Rect getBarContentFrameForWindow(WindowState win, int windowType) {
final Rect rotatedBarFrame = win.mToken.getFixedRotationBarContentFrame(windowType);
- return rotatedBarFrame != null ? rotatedBarFrame : mBarContentFrames.get(windowType);
+ if (rotatedBarFrame != null) {
+ return rotatedBarFrame;
+ }
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ return mBarContentFrames.get(windowType);
+ }
+ // We only need a window specific information for the fixed rotation, use raw insets state
+ // for all other cases.
+ InsetsState insetsState = mDisplayContent.getInsetsStateController().getRawInsetsState();
+ final Rect tmpRect = new Rect();
+ if (windowType == TYPE_NAVIGATION_BAR) {
+ tmpRect.set(insetsState.getSource(InsetsState.ITYPE_NAVIGATION_BAR).getFrame());
+ }
+ if (windowType == TYPE_STATUS_BAR) {
+ tmpRect.set(insetsState.getSource(InsetsState.ITYPE_STATUS_BAR).getFrame());
+ }
+ tmpRect.intersect(mDisplayContent.mDisplayFrames.mDisplayCutoutSafe);
+ return tmpRect;
}
/**
@@ -2905,17 +3147,19 @@ public class DisplayPolicy {
/** @return the current visibility flags with the status bar opacity related flags toggled. */
private int configureStatusBarOpacity(int appearance) {
- final boolean fullscreenDrawsBackground =
- drawsBarBackground(mTopFullscreenOpaqueWindowState);
- final boolean dockedDrawsBackground =
- drawsBarBackground(mTopDockedOpaqueWindowState);
+ boolean drawBackground = true;
+ boolean isFullyTransparentAllowed = true;
+ for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
+ final WindowState window = mStatusBarBackgroundWindows.get(i);
+ drawBackground &= drawsBarBackground(window);
+ isFullyTransparentAllowed &= isFullyTransparentAllowed(window, TYPE_STATUS_BAR);
+ }
- if (fullscreenDrawsBackground && dockedDrawsBackground) {
+ if (drawBackground) {
appearance &= ~APPEARANCE_OPAQUE_STATUS_BARS;
}
- if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_STATUS_BAR)
- || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_STATUS_BAR)) {
+ if (!isFullyTransparentAllowed) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
}
@@ -2926,53 +3170,35 @@ public class DisplayPolicy {
* @return the current visibility flags with the nav-bar opacity related flags toggled based
* on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
*/
- private int configureNavBarOpacity(int appearance, boolean dockedRootTaskVisible,
- boolean isDockedDividerResizing) {
- final boolean freeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
- .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
- final boolean fullscreenDrawsBackground =
- drawsBarBackground(mTopFullscreenOpaqueWindowState);
- final boolean dockedDrawsBackground =
- drawsBarBackground(mTopDockedOpaqueWindowState);
+ private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible,
+ boolean freeformRootTaskVisible) {
+ final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow);
if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) {
- if (fullscreenDrawsBackground && dockedDrawsBackground) {
+ if (drawBackground) {
appearance = clearNavBarOpaqueFlag(appearance);
- } else if (dockedRootTaskVisible) {
- appearance = setNavBarOpaqueFlag(appearance);
}
} else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
- if (dockedRootTaskVisible || freeformRootTaskVisible || isDockedDividerResizing) {
+ if (multiWindowTaskVisible || freeformRootTaskVisible) {
if (mIsFreeformWindowOverlappingWithNavBar) {
appearance = clearNavBarOpaqueFlag(appearance);
- } else {
- appearance = setNavBarOpaqueFlag(appearance);
}
- } else if (fullscreenDrawsBackground) {
+ } else if (drawBackground) {
appearance = clearNavBarOpaqueFlag(appearance);
}
} else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) {
- if (isDockedDividerResizing) {
- appearance = setNavBarOpaqueFlag(appearance);
- } else if (freeformRootTaskVisible) {
+ if (freeformRootTaskVisible) {
appearance = clearNavBarOpaqueFlag(appearance);
- } else {
- appearance = setNavBarOpaqueFlag(appearance);
}
}
- if (!isFullyTransparentAllowed(mTopFullscreenOpaqueWindowState, TYPE_NAVIGATION_BAR)
- || !isFullyTransparentAllowed(mTopDockedOpaqueWindowState, TYPE_NAVIGATION_BAR)) {
+ if (!isFullyTransparentAllowed(mNavBarBackgroundWindow, TYPE_NAVIGATION_BAR)) {
appearance |= APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
}
return appearance;
}
- private int setNavBarOpaqueFlag(int appearance) {
- return appearance | APPEARANCE_OPAQUE_NAVIGATION_BARS;
- }
-
private int clearNavBarOpaqueFlag(int appearance) {
return appearance & ~APPEARANCE_OPAQUE_NAVIGATION_BARS;
}
@@ -3012,7 +3238,7 @@ public class DisplayPolicy {
return;
}
mPendingPanicGestureUptime = SystemClock.uptimeMillis();
- updateSystemUiVisibilityLw();
+ updateSystemBarAttributes();
}
}
};
@@ -3073,6 +3299,7 @@ public class DisplayPolicy {
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.println("DisplayPolicy");
prefix += " ";
+ final String prefixInner = prefix + " ";
pw.print(prefix);
pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer);
pw.print(" mDeskDockEnablesAccelerometer=");
@@ -3140,14 +3367,27 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
pw.println(mTopFullscreenOpaqueWindowState);
}
- if (mTopFullscreenOpaqueOrDimmingWindowState != null) {
- pw.print(prefix); pw.print("mTopFullscreenOpaqueOrDimmingWindowState=");
- pw.println(mTopFullscreenOpaqueOrDimmingWindowState);
+ if (mNavBarColorWindowCandidate != null) {
+ pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
+ pw.println(mNavBarColorWindowCandidate);
}
- if (mForcingShowNavBar) {
- pw.print(prefix); pw.print("mForcingShowNavBar="); pw.println(mForcingShowNavBar);
- pw.print(prefix); pw.print("mForcingShowNavBarLayer=");
- pw.println(mForcingShowNavBarLayer);
+ if (mNavBarBackgroundWindow != null) {
+ pw.print(prefix); pw.print("mNavBarBackgroundWindow=");
+ pw.println(mNavBarBackgroundWindow);
+ }
+ if (!mStatusBarColorWindows.isEmpty()) {
+ pw.print(prefix); pw.println("mStatusBarColorWindows=");
+ for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mStatusBarColorWindows.get(i);
+ pw.print(prefixInner); pw.println(win);
+ }
+ }
+ if (!mStatusBarBackgroundWindows.isEmpty()) {
+ pw.print(prefix); pw.println("mStatusBarBackgroundWindows=");
+ for (int i = mStatusBarBackgroundWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mStatusBarBackgroundWindows.get(i);
+ pw.print(prefixInner); pw.println(win);
+ }
}
pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
@@ -3186,7 +3426,7 @@ public class DisplayPolicy {
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
lp.setFitInsetsTypes(0);
- lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
lp.privateFlags |=
@@ -3226,17 +3466,39 @@ public class DisplayPolicy {
}
void release() {
+ mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener);
mHandler.post(mGestureNavigationSettingsObserver::unregister);
}
@VisibleForTesting
- static boolean isOverlappingWithNavBar(WindowState targetWindow, WindowState navBarWindow) {
- if (navBarWindow == null || !navBarWindow.isVisible()
- || targetWindow.mActivityRecord == null || !targetWindow.isVisible()) {
+ static boolean isOverlappingWithNavBar(@NonNull WindowState win) {
+ if (win.mActivityRecord == null || !win.isVisible()) {
return false;
}
- return Rect.intersects(targetWindow.getFrame(), navBarWindow.getFrame());
+ // When the window is dimming means it's requesting dim layer to its host container, so
+ // checking whether it's overlapping with a navigation bar by its container's bounds.
+ return intersectsAnyInsets(win.isDimming() ? win.getBounds() : win.getFrame(),
+ win.getInsetsState(), Type.navigationBars());
+ }
+
+ /**
+ * Returns whether the given {@param bounds} intersects with any insets of the
+ * provided {@param insetsType}.
+ */
+ private static boolean intersectsAnyInsets(Rect bounds, InsetsState insetsState,
+ @InsetsType int insetsType) {
+ final ArraySet<Integer> internalTypes = InsetsState.toInternalType(insetsType);
+ for (int i = 0; i < internalTypes.size(); i++) {
+ final InsetsSource source = insetsState.peekSource(internalTypes.valueAt(i));
+ if (source == null || !source.isVisible()) {
+ continue;
+ }
+ if (Rect.intersects(bounds, source.getFrame())) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 9f69fd933c16..3f130f5a1457 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -22,7 +22,6 @@ import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFA
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 android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
@@ -484,7 +483,7 @@ public class DisplayRotation {
*/
boolean updateRotationUnchecked(boolean forceUpdate) {
final boolean useShellTransitions =
- mService.mAtmService.getTransitionController().getTransitionPlayer() != null;
+ mDisplayContent.mTransitionController.isShellTransitionsEnabled();
final int displayId = mDisplayContent.getDisplayId();
if (!forceUpdate && !useShellTransitions) {
@@ -513,7 +512,9 @@ public class DisplayRotation {
return false;
}
- if (mDisplayContent.mFixedRotationTransitionListener
+ final RecentsAnimationController recentsAnimController =
+ mService.getRecentsAnimationController();
+ if (recentsAnimController != null && mDisplayContent.mFixedRotationTransitionListener
.isTopFixedOrientationRecentsAnimating()
// If screen is off or the device is going to sleep, then still allow to update.
&& mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
@@ -521,6 +522,7 @@ public class DisplayRotation {
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
// rotation update while the animation is running.
+ recentsAnimController.setCheckRotationAfterCleanup();
return false;
}
}
@@ -562,12 +564,6 @@ public class DisplayRotation {
recentsAnimationController.cancelAnimationForDisplayChange();
}
- final Transition t = (useShellTransitions
- && !mService.mAtmService.getTransitionController().isCollecting())
- ? mService.mAtmService.getTransitionController().createTransition(TRANSIT_CHANGE)
- : null;
- mService.mAtmService.getTransitionController().collect(mDisplayContent);
-
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d rotation changed to %d from %d, lastOrientation=%d",
displayId, rotation, oldRotation, lastOrientation);
@@ -581,11 +577,10 @@ public class DisplayRotation {
mDisplayContent.setLayoutNeeded();
if (useShellTransitions) {
- if (t != null) {
- // This created its own transition, so send a start request.
- mService.mAtmService.getTransitionController().requestStartTransition(
- t, null /* trigger */, null /* remote */);
- } else {
+ final boolean wasInTransition = mDisplayContent.inTransition();
+ mDisplayContent.requestChangeTransitionIfNeeded(
+ ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
+ if (wasInTransition) {
// Use remote-rotation infra since the transition has already been requested
// TODO(shell-transitions): Remove this once lifecycle management can cover all
// rotation cases.
@@ -661,17 +656,17 @@ public class DisplayRotation {
mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
mIsWaitingForRemoteRotation = false;
- if (mService.mAtmService.getTransitionController().getTransitionPlayer() != null) {
- if (!mService.mAtmService.getTransitionController().isCollecting()) {
+ if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ if (!mDisplayContent.mTransitionController.isCollecting()) {
throw new IllegalStateException("Trying to rotate outside a transition");
}
- mService.mAtmService.getTransitionController().collect(mDisplayContent);
+ mDisplayContent.mTransitionController.collect(mDisplayContent);
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
// handling is synchronized.
mDisplayContent.forAllTasks(task -> {
if (task.isVisible()) {
- mService.mAtmService.getTransitionController().collect(task);
+ mDisplayContent.mTransitionController.collect(task);
}
});
mDisplayContent.getInsetsStateController().addProvidersToTransition();
@@ -1408,7 +1403,7 @@ public class DisplayRotation {
case ActivityInfo.SCREEN_ORIENTATION_USER:
case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED:
// Works with any rotation except upside down.
- return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation);
+ return (preferredRotation >= 0) && (preferredRotation != Surface.ROTATION_180);
}
return false;
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 8fcdf2e96889..4a70fa336838 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -264,8 +264,14 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
@NonNull
private static AtomicFile getVendorSettingsFile() {
- final File vendorFile = new File(Environment.getVendorDirectory(),
+ // First look under product path for treblized builds.
+ File vendorFile = new File(Environment.getProductDirectory(),
VENDOR_DISPLAY_SETTINGS_FILE_PATH);
+ if (!vendorFile.exists()) {
+ // Try and look in vendor path.
+ vendorFile = new File(Environment.getVendorDirectory(),
+ VENDOR_DISPLAY_SETTINGS_FILE_PATH);
+ }
return new AtomicFile(vendorFile, WM_DISPLAY_COMMIT_TAG);
}
diff --git a/services/core/java/com/android/server/wm/DockedTaskDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
index fb9d06441536..925a6d858a3d 100644
--- a/services/core/java/com/android/server/wm/DockedTaskDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
@@ -46,7 +46,7 @@ public class DockedTaskDividerController {
void setTouchRegion(Rect touchRegion) {
mTouchRegion.set(touchRegion);
// We need to report touchable region changes to accessibility.
- if (mDisplayContent.mWmService.mAccessibilityController != null) {
+ if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
mDisplayContent.getDisplayId());
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index d12d07ab7448..cc6a8807bb9d 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -22,6 +22,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.content.ClipData;
+import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -32,6 +33,7 @@ import android.view.Display;
import android.view.IWindow;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
@@ -43,7 +45,8 @@ import java.util.concurrent.atomic.AtomicReference;
*/
class DragDropController {
private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
- private static final long DRAG_TIMEOUT_MS = 5000;
+ static final long DRAG_TIMEOUT_MS = 5000;
+ private static final int A11Y_DRAG_TIMEOUT_DEFAULT_MS = 60000;
// Messages for Handler.
static final int MSG_DRAG_END_TIMEOUT = 0;
@@ -151,36 +154,48 @@ class DragDropController {
mDragState.mOriginalAlpha = alpha;
mDragState.mToken = dragToken;
mDragState.mDisplayContent = displayContent;
+ mDragState.mData = data;
- final Display display = displayContent.getDisplay();
- if (!mCallback.get().registerInputChannel(
- mDragState, display, mService.mInputManager,
- callingWin.mInputChannel)) {
- Slog.e(TAG_WM, "Unable to transfer touch focus");
- return null;
- }
+ if ((flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) == 0) {
+ final Display display = displayContent.getDisplay();
+ if (!mCallback.get().registerInputChannel(
+ mDragState, display, mService.mInputManager,
+ callingWin.mInputChannel)) {
+ Slog.e(TAG_WM, "Unable to transfer touch focus");
+ return null;
+ }
- final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
- mDragState.mData = data;
- mDragState.broadcastDragStartedLocked(touchX, touchY);
- mDragState.overridePointerIconLocked(touchSource);
- // remember the thumb offsets for later
- mDragState.mThumbOffsetX = thumbCenterX;
- mDragState.mThumbOffsetY = thumbCenterY;
-
- // Make the surface visible at the proper location
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
-
- final SurfaceControl.Transaction transaction = mDragState.mTransaction;
- transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
- transaction.setPosition(
- surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
- transaction.show(surfaceControl);
- displayContent.reparentToOverlay(transaction, surfaceControl);
- callingWin.scheduleAnimation();
-
- if (SHOW_LIGHT_TRANSACTIONS) {
- Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+ final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+ mDragState.broadcastDragStartedLocked(touchX, touchY);
+ mDragState.overridePointerIconLocked(touchSource);
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
+ }
+
+ final SurfaceControl.Transaction transaction = mDragState.mTransaction;
+ transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+ transaction.setPosition(
+ surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
+ transaction.show(surfaceControl);
+ displayContent.reparentToOverlay(transaction, surfaceControl);
+ callingWin.scheduleAnimation();
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+ }
+ } else {
+ // Skip surface logic for a drag triggered by an AccessibilityAction
+ mDragState.broadcastDragStartedLocked(touchX, touchY);
+
+ // Timeout for the user to drop the content
+ sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, callingWin.mClient.asBinder(),
+ getAccessibilityManager().getRecommendedTimeoutMillis(
+ A11Y_DRAG_TIMEOUT_DEFAULT_MS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
} finally {
if (surface != null) {
@@ -309,10 +324,10 @@ class DragDropController {
/**
* Sends a timeout message to the Handler managed by DragDropController.
*/
- void sendTimeoutMessage(int what, Object arg) {
+ void sendTimeoutMessage(int what, Object arg, long timeoutMs) {
mHandler.removeMessages(what, arg);
final Message msg = mHandler.obtainMessage(what, arg);
- mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
+ mHandler.sendMessageDelayed(msg, timeoutMs);
}
/**
@@ -332,6 +347,30 @@ class DragDropController {
}
}
+ boolean dropForAccessibility(IWindow window, float x, float y) {
+ synchronized (mService.mGlobalLock) {
+ final boolean isA11yEnabled = getAccessibilityManager().isEnabled();
+ if (!dragDropActiveLocked()) {
+ return false;
+ }
+ if (mDragState.isAccessibilityDragDrop() && isA11yEnabled) {
+ final WindowState winState = mService.windowForClientLocked(
+ null, window, false);
+ if (!mDragState.isWindowNotified(winState)) {
+ return false;
+ }
+ IBinder token = winState.mInputChannelToken;
+ return mDragState.reportDropWindowLock(token, x, y);
+ }
+ return false;
+ }
+ }
+
+ AccessibilityManager getAccessibilityManager() {
+ return (AccessibilityManager) mService.mContext.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ }
+
private class DragHandler extends Handler {
/**
* Lock for window manager.
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index aa257f847e25..4fc123d3f68a 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -253,7 +253,7 @@ class DragState {
mTransaction.reparent(mSurfaceControl, null).apply();
} else {
mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT,
- mSurfaceControl);
+ mSurfaceControl, DragDropController.DRAG_TIMEOUT_MS);
}
mSurfaceControl = null;
}
@@ -276,9 +276,9 @@ class DragState {
* Notify the drop target and tells it about the data. If the drop event is not sent to the
* target, invokes {@code endDragLocked} immediately.
*/
- void reportDropWindowLock(IBinder token, float x, float y) {
+ boolean reportDropWindowLock(IBinder token, float x, float y) {
if (mAnimator != null) {
- return;
+ return false;
}
final WindowState touchedWin = mService.mInputToWindowMap.get(token);
@@ -288,7 +288,7 @@ class DragState {
mDragResult = false;
endDragLocked();
if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
- return;
+ return false;
}
if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
@@ -322,16 +322,19 @@ class DragState {
touchedWin.mClient.dispatchDragEvent(event);
// 5 second timeout for this window to respond to the drop
- mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken);
+ mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, clientToken,
+ DragDropController.DRAG_TIMEOUT_MS);
} catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
endDragLocked();
+ return false;
} finally {
if (myPid != touchedWin.mSession.mPid) {
event.recycle();
}
}
mToken = clientToken;
+ return true;
}
class InputInterceptor {
@@ -553,7 +556,7 @@ class DragState {
}
}
- private boolean isWindowNotified(WindowState newWin) {
+ boolean isWindowNotified(WindowState newWin) {
for (WindowState ws : mNotifiedWindows) {
if (ws == newWin) {
return true;
@@ -567,8 +570,10 @@ class DragState {
return;
}
if (!mDragResult) {
- mAnimator = createReturnAnimationLocked();
- return; // Will call closeLocked() when the animation is done.
+ if (!isAccessibilityDragDrop()) {
+ mAnimator = createReturnAnimationLocked();
+ return; // Will call closeLocked() when the animation is done.
+ }
}
closeLocked();
}
@@ -577,7 +582,7 @@ class DragState {
if (mAnimator != null) {
return;
}
- if (!mDragInProgress || skipAnimation) {
+ if (!mDragInProgress || skipAnimation || isAccessibilityDragDrop()) {
// mDragInProgress is false if an app invokes Session#cancelDragAndDrop before
// Session#performDrag. Reset the drag state without playing the cancel animation
// because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
@@ -722,4 +727,8 @@ class DragState {
mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
}
}
+
+ boolean isAccessibilityDragDrop() {
+ return (mFlags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index b08d6e1dff9e..fc317a1212d5 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -127,7 +127,7 @@ class EmbeddedWindowController {
}
}
- static class EmbeddedWindow {
+ static class EmbeddedWindow implements InputTarget {
final IWindow mClient;
@Nullable final WindowState mHostWindowState;
@Nullable final ActivityRecord mHostActivityRecord;
@@ -166,7 +166,8 @@ class EmbeddedWindowController {
mDisplayId = displayId;
}
- String getName() {
+ @Override
+ public String toString() {
final String hostWindowName = (mHostWindowState != null)
? mHostWindowState.getWindowTag().toString() : "Internal";
return "EmbeddedWindow{ u" + UserHandle.getUserId(mOwnerUid) + " " + hostWindowName
@@ -183,7 +184,7 @@ class EmbeddedWindowController {
}
InputChannel openInputChannel() {
- final String name = getName();
+ final String name = toString();
mInputChannel = mWmService.mInputManager.createInputChannel(name);
return mInputChannel;
}
@@ -195,5 +196,25 @@ class EmbeddedWindowController {
mInputChannel = null;
}
}
+
+ @Override
+ public WindowState getWindowState() {
+ return mHostWindowState;
+ }
+
+ @Override
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ @Override
+ public IWindow getIWindow() {
+ return mClient;
+ }
+
+ @Override
+ public int getPid() {
+ return mOwnerPid;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 316c20ba5c47..badb1f5a0a12 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -16,30 +16,33 @@
package com.android.server.wm;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.Task.TAG_VISIBILITY;
import android.annotation.Nullable;
import android.util.Slog;
+import java.util.ArrayList;
+
/** Helper class to ensure activities are in the right visible state for a container. */
class EnsureActivitiesVisibleHelper {
- private final Task mTask;
+ private final TaskFragment mTaskFragment;
private ActivityRecord mTop;
private ActivityRecord mStarting;
private boolean mAboveTop;
private boolean mContainerShouldBeVisible;
- private boolean mBehindFullscreenActivity;
+ private boolean mBehindFullyOccludedContainer;
private int mConfigChanges;
private boolean mPreserveWindows;
private boolean mNotifyClients;
- EnsureActivitiesVisibleHelper(Task container) {
- mTask = container;
+ EnsureActivitiesVisibleHelper(TaskFragment container) {
+ mTaskFragment = container;
}
/**
- * Update all attributes except {@link mTask} to use in subsequent calculations.
+ * Update all attributes except {@link mTaskFragment} to use in subsequent calculations.
*
* @param starting The activity that is being started
* @param configChanges Parts of the configuration that changed for this activity for evaluating
@@ -51,12 +54,12 @@ class EnsureActivitiesVisibleHelper {
void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
boolean notifyClients) {
mStarting = starting;
- mTop = mTask.topRunningActivity();
+ mTop = mTaskFragment.topRunningActivity();
// If the top activity is not fullscreen, then we need to make sure any activities under it
// are now visible.
mAboveTop = mTop != null;
- mContainerShouldBeVisible = mTask.shouldBeVisible(mStarting);
- mBehindFullscreenActivity = !mContainerShouldBeVisible;
+ mContainerShouldBeVisible = mTaskFragment.shouldBeVisible(mStarting);
+ mBehindFullyOccludedContainer = !mContainerShouldBeVisible;
mConfigChanges = configChanges;
mPreserveWindows = preserveWindows;
mNotifyClients = notifyClients;
@@ -85,22 +88,59 @@ class EnsureActivitiesVisibleHelper {
Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + mTop
+ " configChanges=0x" + Integer.toHexString(configChanges));
}
- if (mTop != null) {
- mTask.checkTranslucentActivityWaiting(mTop);
+ if (mTop != null && mTaskFragment.asTask() != null) {
+ // TODO(14709632): Check if this needed to be implemented in TaskFragment.
+ mTaskFragment.asTask().checkTranslucentActivityWaiting(mTop);
}
// We should not resume activities that being launched behind because these
// activities are actually behind other fullscreen activities, but still required
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
- && mTask.isTopActivityFocusable()
- && (starting == null || !starting.isDescendantOf(mTask));
-
- mTask.forAllActivities(a -> {
- setActivityVisibilityState(a, starting, resumeTopActivity);
- });
- if (mTask.mAtmService.getTransitionController().getTransitionPlayer() != null) {
- mTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
+ && mTaskFragment.isTopActivityFocusable()
+ && (starting == null || !starting.isDescendantOf(mTaskFragment));
+
+ ArrayList<TaskFragment> adjacentTaskFragments = null;
+ for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mTaskFragment.mChildren.get(i);
+ final TaskFragment childTaskFragment = child.asTaskFragment();
+ if (childTaskFragment != null && childTaskFragment.topRunningActivity() != null) {
+ childTaskFragment.updateActivityVisibilities(starting, configChanges,
+ preserveWindows, notifyClients);
+ mBehindFullyOccludedContainer |=
+ childTaskFragment.getBounds().equals(mTaskFragment.getBounds());
+ if (mAboveTop && mTop.getTaskFragment() == childTaskFragment) {
+ mAboveTop = false;
+ }
+
+ if (mBehindFullyOccludedContainer) {
+ continue;
+ }
+
+ if (adjacentTaskFragments != null && adjacentTaskFragments.contains(
+ childTaskFragment)) {
+ if (!childTaskFragment.isTranslucent(starting)
+ && !childTaskFragment.getAdjacentTaskFragment().isTranslucent(
+ starting)) {
+ // Everything behind two adjacent TaskFragments are occluded.
+ mBehindFullyOccludedContainer = true;
+ }
+ continue;
+ }
+
+ final TaskFragment adjacentTaskFrag = childTaskFragment.getAdjacentTaskFragment();
+ if (adjacentTaskFrag != null) {
+ if (adjacentTaskFragments == null) {
+ adjacentTaskFragments = new ArrayList<>();
+ }
+ adjacentTaskFragments.add(adjacentTaskFrag);
+ }
+ } else if (child.asActivityRecord() != null) {
+ setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity);
+ }
+ }
+ if (mTaskFragment.mTransitionController.isShellTransitionsEnabled()) {
+ mTaskFragment.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
}
}
@@ -112,7 +152,7 @@ class EnsureActivitiesVisibleHelper {
}
mAboveTop = false;
- r.updateVisibilityIgnoringKeyguard(mBehindFullscreenActivity);
+ r.updateVisibilityIgnoringKeyguard(mBehindFullyOccludedContainer);
final boolean reallyVisible = r.shouldBeVisibleUnchecked();
// Check whether activity should be visible without Keyguard influence
@@ -122,12 +162,14 @@ class EnsureActivitiesVisibleHelper {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
+ " containerVisible=" + mContainerShouldBeVisible
- + " behindFullscreen=" + mBehindFullscreenActivity);
+ + " behindFullyOccluded=" + mBehindFullyOccludedContainer);
}
- mBehindFullscreenActivity = true;
+ mBehindFullyOccludedContainer = true;
} else {
- mBehindFullscreenActivity = false;
+ mBehindFullyOccludedContainer = false;
}
+ } else if (r.isState(INITIALIZING)) {
+ r.cancelInitializing();
}
if (reallyVisible) {
@@ -173,24 +215,25 @@ class EnsureActivitiesVisibleHelper {
Slog.v(TAG_VISIBILITY, "Make invisible? " + r
+ " finishing=" + r.finishing + " state=" + r.getState()
+ " containerShouldBeVisible=" + mContainerShouldBeVisible
- + " behindFullscreenActivity=" + mBehindFullscreenActivity
+ + " behindFullyOccludedContainer=" + mBehindFullyOccludedContainer
+ " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
}
r.makeInvisible();
}
- if (!mBehindFullscreenActivity && mTask.isActivityTypeHome() && r.isRootOfTask()) {
+ if (!mBehindFullyOccludedContainer && mTaskFragment.isActivityTypeHome()
+ && r.isRootOfTask()) {
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Home task: at " + mTask
+ Slog.v(TAG_VISIBILITY, "Home task: at " + mTaskFragment
+ " containerShouldBeVisible=" + mContainerShouldBeVisible
- + " behindFullscreenActivity=" + mBehindFullscreenActivity);
+ + " behindOccludedParentContainer=" + mBehindFullyOccludedContainer);
}
// No other task in the root home task should be visible behind the home activity.
// Home activities is usually a translucent activity with the wallpaper behind
// them. However, when they don't have the wallpaper behind them, we want to
// show activities in the next application root task behind them vs. another
// task in the root home task like recents.
- mBehindFullscreenActivity = true;
+ mBehindFullyOccludedContainer = true;
}
}
@@ -219,7 +262,8 @@ class EnsureActivitiesVisibleHelper {
r.setVisibility(true);
}
if (r != starting) {
- mTask.mTaskSupervisor.startSpecificActivity(r, andResume, true /* checkConfig */);
+ mTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume,
+ true /* checkConfig */);
}
}
}
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
index eab3f108d17a..52a7ac75e2dc 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -42,6 +42,9 @@ public class FadeRotationAnimationController extends FadeAnimationController {
/** A runnable which gets called when the {@link #show()} is called. */
private Runnable mOnShowRunnable;
+ /** Whether to use constant zero alpha animation. */
+ private boolean mHideImmediately;
+
public FadeRotationAnimationController(DisplayContent displayContent) {
super(displayContent);
mService = displayContent.mWmService;
@@ -51,6 +54,10 @@ public class FadeRotationAnimationController extends FadeAnimationController {
mService.mWindowPlacerLocked.performSurfacePlacement();
}
} : null;
+ if (mFrozenTimeoutRunnable != null) {
+ // Hide the windows immediately because screen should have been covered by screenshot.
+ mHideImmediately = true;
+ }
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
final WindowState navigationBar = displayPolicy.getNavigationBar();
if (navigationBar != null) {
@@ -120,6 +127,15 @@ public class FadeRotationAnimationController extends FadeAnimationController {
}
}
+ /** Hides the window immediately until it is drawn in new rotation. */
+ void hideImmediately(WindowToken windowToken) {
+ final boolean original = mHideImmediately;
+ mHideImmediately = true;
+ mTargetWindowTokens.add(windowToken);
+ fadeWindowToken(false /* show */, windowToken, ANIMATION_TYPE_FIXED_TRANSFORM);
+ mHideImmediately = original;
+ }
+
/** Returns {@code true} if the window is handled by this controller. */
boolean isHandledToken(WindowToken token) {
return token == mNavBarToken || isTargetToken(token);
@@ -145,8 +161,7 @@ public class FadeRotationAnimationController extends FadeAnimationController {
@Override
public Animation getFadeOutAnimation() {
- if (mFrozenTimeoutRunnable != null) {
- // Hide the window immediately because screen should have been covered by screenshot.
+ if (mHideImmediately) {
return new AlphaAnimation(0 /* fromAlpha */, 0 /* toAlpha */);
}
return super.getFadeOutAnimation();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index ed1e784bf275..cbefe7f3ade4 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -29,6 +29,7 @@ import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_D
import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
@@ -91,6 +92,16 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
}
@Override
+ void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+ if (target != null && target.getWindow() != null) {
+ // ime control target could be a different window.
+ // Refer WindowState#getImeControlTarget().
+ target = target.getWindow().getImeControlTarget();
+ }
+ super.updateControlForTarget(target, force);
+ }
+
+ @Override
protected boolean updateClientVisibility(InsetsControlTarget caller) {
boolean changed = super.updateClientVisibility(caller);
if (changed && caller.getRequestedVisibility(mSource.getType())) {
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 747d3652e150..f3b9cdfd39e0 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -20,6 +20,7 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
@@ -420,7 +421,7 @@ public class ImmersiveModeConfirmation {
}
final Bundle options = new Bundle();
- options.putInt(DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
+ options.putInt(KEY_ROOT_DISPLAY_AREA_ID, rootDisplayAreaId);
return options;
}
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index aa7e6c9c80fc..18a2c601f6d3 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -30,6 +30,7 @@ import android.util.Slog;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
+import android.view.WindowManagerPolicyConstants;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.input.InputManagerService;
@@ -181,8 +182,8 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal
@Override
public int getPointerLayer() {
return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_POINTER)
- * WindowManagerService.TYPE_LAYER_MULTIPLIER
- + WindowManagerService.TYPE_LAYER_OFFSET;
+ * WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER
+ + WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
}
/** Callback to get pointer display id. */
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index d417d56b6d31..6afd3355b0a1 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -48,7 +48,9 @@ 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;
-import android.graphics.Rect;
+import static java.lang.Integer.MAX_VALUE;
+
+import android.annotation.Nullable;
import android.graphics.Region;
import android.os.Handler;
import android.os.IBinder;
@@ -67,6 +69,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.Set;
import java.util.function.Consumer;
@@ -101,6 +104,15 @@ final class InputMonitor {
private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap();
/**
+ * Set when recents (overview) is active as part of a shell transition. While set, any focus
+ * going to the recents activity will be redirected to the Recents input consumer. Since we
+ * draw the live-tile above the recents activity, we also need to provide that activity as a
+ * z-layering reference so that we can place the recents input consumer above it.
+ */
+ private WeakReference<ActivityRecord> mActiveRecentsActivity = null;
+ private WeakReference<ActivityRecord> mActiveRecentsLayerRef = null;
+
+ /**
* Representation of a input consumer that the policy has added to the window manager to consume
* input events going to windows below it.
*/
@@ -279,6 +291,7 @@ final class InputMonitor {
inputWindowHandle.setInputFeatures(w.mAttrs.inputFeatures);
inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
inputWindowHandle.setVisible(w.isVisible());
+ inputWindowHandle.setWindowToken(w.mClient);
final boolean focusable = w.canReceiveKeys()
&& (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
@@ -289,9 +302,6 @@ final class InputMonitor {
&& !mDisableWallpaperTouchEvents;
inputWindowHandle.setHasWallpaper(hasWallpaper);
- final Rect frame = w.getFrame();
- inputWindowHandle.setFrame(frame.left, frame.top, frame.right, frame.bottom);
-
// Surface insets are hardcoded to be the same in all directions
// and we could probably deprecate the "left/right/top/bottom" concept.
// we avoid reintroducing this concept by just choosing one of them here.
@@ -301,33 +311,46 @@ final class InputMonitor {
// what is on screen to what is actually being touched in the UI.
inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f);
- final int flags = w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs.flags);
- inputWindowHandle.setTouchableRegion(mTmpRegion);
+ // Update layout params flags to force the window to be not touch modal. We do this to
+ // restrict the window's touchable region to the task even if it request touches outside its
+ // window bounds. An example is a dialog in primary split should get touches outside its
+ // window within the primary task but should not get any touches going to the secondary
+ // task.
+ int flags = w.mAttrs.flags;
+ if (w.mAttrs.isModal()) {
+ flags = flags | FLAG_NOT_TOUCH_MODAL;
+ }
inputWindowHandle.setLayoutParamsFlags(flags);
- boolean useSurfaceCrop = false;
+ boolean useSurfaceBoundsAsTouchRegion = false;
+ SurfaceControl touchableRegionCrop = null;
final Task task = w.getTask();
if (task != null) {
- if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ // TODO(b/165794636): Remove the special case for freeform window once drag resizing is
+ // handled by WM shell.
+ if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ && !task.inFreeformWindowingMode()) {
// If the window is in a TaskManaged by a TaskOrganizer then most cropping will
// be applied using the SurfaceControl hierarchy from the Organizer. This means
// we need to make sure that these changes in crop are reflected in the input
// windows, and so ensure this flag is set so that the input crop always reflects
// the surface hierarchy.
- // TODO(b/168252846): we have some issues with modal-windows, so we need to cross
- // that bridge now that we organize full-screen Tasks.
- inputWindowHandle.setTouchableRegionCrop(null /* Use this surfaces crop */);
- inputWindowHandle.setReplaceTouchableRegionWithCrop(true);
- useSurfaceCrop = true;
+ useSurfaceBoundsAsTouchRegion = true;
+
+ if (w.mAttrs.isModal()) {
+ TaskFragment parent = w.getTaskFragment();
+ touchableRegionCrop = parent != null ? parent.getSurfaceControl() : null;
+ }
} else if (task.cropWindowsToRootTaskBounds() && !w.inFreeformWindowingMode()) {
- inputWindowHandle.setTouchableRegionCrop(task.getRootTask().getSurfaceControl());
- inputWindowHandle.setReplaceTouchableRegionWithCrop(false);
- useSurfaceCrop = true;
+ touchableRegionCrop = task.getRootTask().getSurfaceControl();
}
}
- if (!useSurfaceCrop) {
- inputWindowHandle.setReplaceTouchableRegionWithCrop(false);
- inputWindowHandle.setTouchableRegionCrop(null);
+ inputWindowHandle.setReplaceTouchableRegionWithCrop(useSurfaceBoundsAsTouchRegion);
+ inputWindowHandle.setTouchableRegionCrop(touchableRegionCrop);
+
+ if (!useSurfaceBoundsAsTouchRegion) {
+ w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs);
+ inputWindowHandle.setTouchableRegion(mTmpRegion);
}
}
@@ -393,6 +416,21 @@ final class InputMonitor {
}
/**
+ * Inform InputMonitor when recents is active so it can enable the recents input consumer.
+ * @param activity The active recents activity. {@code null} means recents is not active.
+ * @param layer An activity whose Z-layer is used as a reference for how to sort the consumer.
+ */
+ void setActiveRecents(@Nullable ActivityRecord activity, @Nullable ActivityRecord layer) {
+ final boolean clear = activity == null;
+ mActiveRecentsActivity = clear ? null : new WeakReference<>(activity);
+ mActiveRecentsLayerRef = clear ? null : new WeakReference<>(layer);
+ }
+
+ private static <T> T getWeak(WeakReference<T> ref) {
+ return ref != null ? ref.get() : null;
+ }
+
+ /**
* Called when the current input focus changes.
*/
private void updateInputFocusRequest(InputConsumerImpl recentsAnimationInputConsumer) {
@@ -402,8 +440,10 @@ final class InputMonitor {
if (recentsAnimationInputConsumer != null && focus != null) {
final RecentsAnimationController recentsAnimationController =
mService.getRecentsAnimationController();
- final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord);
+ final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord))
+ // Shell transitions doesn't use RecentsAnimationController
+ || getWeak(mActiveRecentsActivity) != null;
if (shouldApplyRecentsInputConsumer) {
requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
recentsAnimationInputConsumer.mName);
@@ -503,6 +543,14 @@ final class InputMonitor {
mInDrag = inDrag;
resetInputConsumers(mInputTransaction);
+ // Update recents input consumer layer if active
+ if (mAddRecentsAnimationInputConsumerHandle
+ && getWeak(mActiveRecentsActivity) != null) {
+ final WindowContainer layer = getWeak(mActiveRecentsLayerRef);
+ mRecentsAnimationInputConsumer.show(mInputTransaction,
+ layer != null ? layer : getWeak(mActiveRecentsActivity));
+ mAddRecentsAnimationInputConsumerHandle = false;
+ }
mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
updateInputFocusRequest(mRecentsAnimationInputConsumer);
@@ -537,11 +585,17 @@ final class InputMonitor {
final int privateFlags = w.mAttrs.privateFlags;
+ // This only works for legacy transitions.
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
mRecentsAnimationInputConsumer.mWindowHandle)) {
- mRecentsAnimationInputConsumer.show(mInputTransaction, w.mActivityRecord);
- mAddRecentsAnimationInputConsumerHandle = false;
+ final DisplayArea targetDA =
+ recentsAnimationController.getTargetAppDisplayArea();
+ if (targetDA != null) {
+ mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
+ mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
+ mAddRecentsAnimationInputConsumerHandle = false;
+ }
}
}
@@ -552,7 +606,7 @@ final class InputMonitor {
rootTask.getSurfaceControl());
// We set the layer to z=MAX-1 so that it's always on top.
mPipInputConsumer.reparent(mInputTransaction, rootTask);
- mPipInputConsumer.show(mInputTransaction, Integer.MAX_VALUE - 1);
+ mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
mAddPipInputConsumerHandle = false;
}
}
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
new file mode 100644
index 000000000000..c7d328a2b18a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -0,0 +1,40 @@
+/*
+ * 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.wm;
+
+import android.view.IWindow;
+
+/**
+ * Common interface between focusable objects.
+ *
+ * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties
+ * of both targets.
+ */
+interface InputTarget {
+ /* Get the WindowState associated with the target. */
+ WindowState getWindowState();
+
+ /* Display id of the target. */
+ int getDisplayId();
+
+ /* Client IWindow for the target. */
+ IWindow getIWindow();
+
+ /* Owning pid of the target. */
+ int getPid();
+}
+
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 7a4d13c2d697..0a24d3c69b17 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Region;
import android.os.IBinder;
+import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
@@ -275,6 +276,14 @@ class InputWindowHandleWrapper {
mChanged = true;
}
+ void setWindowToken(IWindow windowToken) {
+ if (mHandle.getWindow() == windowToken) {
+ return;
+ }
+ mHandle.setWindowToken(windowToken);
+ mChanged = true;
+ }
+
@Override
public String toString() {
return mHandle + ", changed=" + mChanged;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index a8e1c1cda72b..10ae152c3365 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -18,8 +18,6 @@ package com.android.server.wm;
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
@@ -45,16 +43,20 @@ import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InternalInsetsAnimationController;
import android.view.SurfaceControl;
import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.WindowInsets.Type;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
import android.view.WindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.DisplayThread;
+import com.android.server.statusbar.StatusBarManagerInternal;
/**
* Policy that implements who gets control over the windows generating insets.
@@ -135,15 +137,19 @@ class InsetsPolicy {
abortTransient();
}
mFocusedWin = focusedWin;
- boolean forceShowsSystemBarsForWindowingMode = forceShowsSystemBarsForWindowingMode();
- InsetsControlTarget statusControlTarget = getStatusControlTarget(focusedWin,
- forceShowsSystemBarsForWindowingMode);
- InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin,
- forceShowsSystemBarsForWindowingMode);
- mStateController.onBarControlTargetChanged(statusControlTarget,
- getFakeControlTarget(focusedWin, statusControlTarget),
+ final InsetsControlTarget statusControlTarget =
+ getStatusControlTarget(focusedWin, false /* fake */);
+ final InsetsControlTarget navControlTarget =
+ getNavControlTarget(focusedWin, false /* fake */);
+ mStateController.onBarControlTargetChanged(
+ statusControlTarget,
+ statusControlTarget == mDummyControlTarget
+ ? getStatusControlTarget(focusedWin, true /* fake */)
+ : null,
navControlTarget,
- getFakeControlTarget(focusedWin, navControlTarget));
+ navControlTarget == mDummyControlTarget
+ ? getNavControlTarget(focusedWin, true /* fake */)
+ : null);
mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
}
@@ -153,7 +159,7 @@ class InsetsPolicy {
return provider != null && provider.hasWindow() && !provider.getSource().isVisible();
}
- void showTransient(@InternalInsetsType int[] types) {
+ void showTransient(@InternalInsetsType int[] types, boolean isGestureOnSystemBar) {
boolean changed = false;
for (int i = types.length - 1; i >= 0; i--) {
final @InternalInsetsType int type = types[i];
@@ -167,8 +173,12 @@ class InsetsPolicy {
changed = true;
}
if (changed) {
- mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(),
- mShowingTransientTypes.toArray());
+ StatusBarManagerInternal statusBarManagerInternal =
+ mPolicy.getStatusBarManagerInternal();
+ if (statusBarManagerInternal != null) {
+ statusBarManagerInternal.showTransient(mDisplayContent.getDisplayId(),
+ mShowingTransientTypes.toArray(), isGestureOnSystemBar);
+ }
updateBarControlTarget(mFocusedWin);
// The leashes can be created while updating bar control target. The surface transaction
@@ -303,9 +313,11 @@ class InsetsPolicy {
abortTypes.add(type);
}
}
- if (abortTypes.size() > 0) {
- mPolicy.getStatusBarManagerInternal().abortTransient(mDisplayContent.getDisplayId(),
- abortTypes.toArray());
+ StatusBarManagerInternal statusBarManagerInternal =
+ mPolicy.getStatusBarManagerInternal();
+ if (abortTypes.size() > 0 && statusBarManagerInternal != null) {
+ statusBarManagerInternal.abortTransient(
+ mDisplayContent.getDisplayId(), abortTypes.toArray());
}
}
}
@@ -315,19 +327,17 @@ class InsetsPolicy {
* updateBarControlTarget(mFocusedWin) after this invocation.
*/
private void abortTransient() {
- mPolicy.getStatusBarManagerInternal().abortTransient(mDisplayContent.getDisplayId(),
- mShowingTransientTypes.toArray());
+ StatusBarManagerInternal statusBarManagerInternal = mPolicy.getStatusBarManagerInternal();
+ if (statusBarManagerInternal != null) {
+ statusBarManagerInternal.abortTransient(
+ mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray());
+ }
mShowingTransientTypes.clear();
}
- private @Nullable InsetsControlTarget getFakeControlTarget(@Nullable WindowState focused,
- InsetsControlTarget realControlTarget) {
- return realControlTarget == mDummyControlTarget ? focused : null;
- }
-
private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
- boolean forceShowsSystemBarsForWindowingMode) {
- if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
+ boolean fake) {
+ if (!fake && isShowingTransientTypes(Type.statusBars())) {
return mDummyControlTarget;
}
final WindowState notificationShade = mPolicy.getNotificationShade();
@@ -340,13 +350,12 @@ class InsetsPolicy {
focusedWin.mAttrs.packageName);
return mDisplayContent.mRemoteInsetsControlTarget;
}
- if (forceShowsSystemBarsForWindowingMode) {
- // Status bar is forcibly shown for the windowing mode which is a steady state.
- // We don't want the client to control the status bar, and we will dispatch the real
- // visibility of status bar to the client.
+ if (mPolicy.areSystemBarsForcedShownLw()) {
+ // Status bar is forcibly shown. We don't want the client to control the status bar, and
+ // we will dispatch the real visibility of status bar to the client.
return null;
}
- if (forceShowsStatusBarTransiently()) {
+ if (forceShowsStatusBarTransiently() && !fake) {
// Status bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
@@ -372,13 +381,13 @@ class InsetsPolicy {
}
private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin,
- boolean forceShowsSystemBarsForWindowingMode) {
+ boolean fake) {
final WindowState imeWin = mDisplayContent.mInputMethodWindow;
if (imeWin != null && imeWin.isVisible()) {
// Force showing navigation bar while IME is visible.
return null;
}
- if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1) {
+ if (!fake && isShowingTransientTypes(Type.navigationBars())) {
return mDummyControlTarget;
}
if (focusedWin == mPolicy.getNotificationShade()) {
@@ -390,13 +399,12 @@ class InsetsPolicy {
focusedWin.mAttrs.packageName);
return mDisplayContent.mRemoteInsetsControlTarget;
}
- if (forceShowsSystemBarsForWindowingMode) {
- // Navigation bar is forcibly shown for the windowing mode which is a steady state.
- // We don't want the client to control the navigation bar, and we will dispatch the real
- // visibility of navigation bar to the client.
+ if (mPolicy.areSystemBarsForcedShownLw()) {
+ // Navigation bar is forcibly shown. We don't want the client to control the navigation
+ // bar, and we will dispatch the real visibility of navigation bar to the client.
return null;
}
- if (forceShowsNavigationBarTransiently()) {
+ if (forceShowsNavigationBarTransiently() && !fake) {
// Navigation bar is forcibly shown transiently, and its new visibility won't be
// dispatched to the client so that we can keep the layout stable. We will dispatch the
// fake control to the client, so that it can re-show the bar during this scenario.
@@ -405,6 +413,16 @@ class InsetsPolicy {
return focusedWin;
}
+ private boolean isShowingTransientTypes(@Type.InsetsType int types) {
+ final IntArray showingTransientTypes = mShowingTransientTypes;
+ for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
+ if ((InsetsState.toPublicType(showingTransientTypes.get(i)) & types) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Determines whether the remote insets controller should take control of system bars for all
* windows.
@@ -438,19 +456,6 @@ class InsetsPolicy {
&& (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
}
- private boolean forceShowsSystemBarsForWindowingMode() {
- final boolean isDockedRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
- .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- final boolean isFreeformRootTaskVisible = mDisplayContent.getDefaultTaskDisplayArea()
- .isRootTaskVisible(WINDOWING_MODE_FREEFORM);
- final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing();
-
- // We need to force system bars when the docked root task is visible, when the freeform
- // root task is visible but also when we are resizing for the transitions when docked
- // root task visibility changes.
- return isDockedRootTaskVisible || isFreeformRootTaskVisible || isResizing;
- }
-
@VisibleForTesting
void startAnimation(boolean show, Runnable callback) {
int typesReady = 0;
@@ -495,8 +500,12 @@ class InsetsPolicy {
final int state = visible ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN;
if (mState != state) {
mState = state;
- mPolicy.getStatusBarManagerInternal().setWindowState(
- mDisplayContent.getDisplayId(), mId, state);
+ StatusBarManagerInternal statusBarManagerInternal =
+ mPolicy.getStatusBarManagerInternal();
+ if (statusBarManagerInternal != null) {
+ statusBarManagerInternal.setWindowState(
+ mDisplayContent.getDisplayId(), mId, state);
+ }
}
}
}
@@ -588,8 +597,8 @@ class InsetsPolicy {
}
@Override
- public void startAnimation(InsetsAnimationControlImpl controller,
- WindowInsetsAnimationControlListener listener, int types,
+ public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
+ void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types,
WindowInsetsAnimation animation,
Bounds bounds) {
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7daebff2ccc2..3948eeec20b0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -22,7 +22,7 @@ import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.internal.protolog.ProtoLogGroup.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;
@@ -108,6 +108,16 @@ class InsetsSourceProvider {
private final boolean mControllable;
+ /**
+ * Whether to forced the dimensions of the source window to the inset frame and crop out any
+ * overflow.
+ * Used to crop the taskbar inset source when a task animation is occurring to hide the taskbar
+ * rounded corners overlays.
+ *
+ * TODO: Remove when we enable shell transitions (b/202383002)
+ */
+ private boolean mCropToProvidingInsets = false;
+
InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
DisplayContent displayContent) {
mClientVisible = InsetsState.getDefaultVisibility(source.getType());
@@ -163,8 +173,10 @@ class InsetsSourceProvider {
// animate-out as new one animates-in.
mWin.cancelAnimation();
mWin.mProvidedInsetsSources.remove(mSource.getType());
+ mSeamlessRotating = false;
}
- ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
+ ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s", win,
+ InsetsState.typeToString(mSource.getType()));
mWin = win;
mFrameProvider = frameProvider;
mImeFrameProvider = imeFrameProvider;
@@ -266,7 +278,7 @@ class InsetsSourceProvider {
&& mWin.okToDisplay()) {
mWin.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
- mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
+ mSetLeashPositionConsumer.accept(mWin.getSyncTransaction());
}
}
if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
@@ -301,17 +313,68 @@ class InsetsSourceProvider {
mFakeControlTarget = fakeTarget;
}
+ /**
+ * Ensures that the inset source window is cropped so that anything that doesn't fit within the
+ * inset frame is cropped out until removeCropToProvidingInsetsBounds is called.
+ *
+ * The inset source surface will get cropped to the be of the size of the insets it's providing.
+ *
+ * For example, for the taskbar window which serves as the ITYPE_EXTRA_NAVIGATION_BAR inset
+ * source, the window is larger than the insets because of the rounded corners overlay, but
+ * during task animations we want to make sure that the overlay is cropped out of the window so
+ * that they don't hide the window animations.
+ *
+ * @param t The transaction to use to apply immediate overflow cropping operations.
+ *
+ * NOTE: The relies on the inset source window to have a leash (usually this would be a leash
+ * for the ANIMATION_TYPE_INSETS_CONTROL animation if the inset is controlled by the client)
+ *
+ * TODO: Remove when we migrate over to shell transitions (b/202383002)
+ */
+ void setCropToProvidingInsetsBounds(Transaction t) {
+ mCropToProvidingInsets = true;
+
+ if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) {
+ // apply to existing leash
+ t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, getProvidingInsetsBoundsCropRect());
+ }
+ }
+
+ /**
+ * Removes any overflow cropping and future cropping to the inset source window's leash that may
+ * have been set with a call to setCropToProvidingInsetsBounds().
+ * @param t The transaction to use to apply immediate removal of overflow cropping.
+ *
+ * TODO: Remove when we migrate over to shell transitions (b/202383002)
+ */
+ void removeCropToProvidingInsetsBounds(Transaction t) {
+ mCropToProvidingInsets = false;
+
+ // apply to existing leash
+ if (mWin != null && mWin.mSurfaceAnimator.hasLeash()) {
+ t.setWindowCrop(mWin.mSurfaceAnimator.mLeash, null);
+ }
+ }
+
+ private Rect getProvidingInsetsBoundsCropRect() {
+ Rect sourceWindowFrame = mWin.getFrame();
+ Rect insetFrame = getSource().getFrame();
+
+ // The rectangle in buffer space we want to crop to
+ return new Rect(
+ insetFrame.left - sourceWindowFrame.left,
+ insetFrame.top - sourceWindowFrame.top,
+ insetFrame.right - sourceWindowFrame.left,
+ insetFrame.bottom - sourceWindowFrame.top
+ );
+ }
+
void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
if (mSeamlessRotating) {
// We are un-rotating the window against the display rotation. We don't want the target
// to control the window for now.
return;
}
- if (target != null && target.getWindow() != null) {
- // ime control target could be a different window.
- // Refer WindowState#getImeControlTarget().
- target = target.getWindow().getImeControlTarget();
- }
if (mWin != null && mWin.getSurfaceControl() == null) {
// if window doesn't have a surface, set it null and return.
@@ -335,7 +398,7 @@ class InsetsSourceProvider {
if (getSource().getType() == ITYPE_IME) {
setClientVisible(target.getRequestedVisibility(mSource.getType()));
}
- final Transaction t = mDisplayContent.getPendingTransaction();
+ final Transaction t = mDisplayContent.getSyncTransaction();
mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
ANIMATION_TYPE_INSETS_CONTROL);
@@ -348,7 +411,7 @@ class InsetsSourceProvider {
updateVisibility();
mControl = new InsetsSourceControl(mSource.getType(), leash, surfacePosition,
mSource.calculateInsets(mWin.getBounds(), true /* ignoreVisibility */));
- ProtoLog.d(WM_DEBUG_IME,
+ ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
}
@@ -381,8 +444,11 @@ class InsetsSourceProvider {
return;
}
mClientVisible = clientVisible;
- mDisplayContent.mWmService.mH.obtainMessage(
- LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
+ if (!mDisplayContent.mLayoutAndAssignWindowLayersScheduled) {
+ mDisplayContent.mLayoutAndAssignWindowLayersScheduled = true;
+ mDisplayContent.mWmService.mH.obtainMessage(
+ LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED, mDisplayContent).sendToTarget();
+ }
updateVisibility();
}
@@ -394,8 +460,9 @@ class InsetsSourceProvider {
protected void updateVisibility() {
mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
- ProtoLog.d(WM_DEBUG_IME,
- "InsetsSource updateVisibility serverVisible: %s clientVisible: %s",
+ ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
+ "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
+ InsetsState.typeToString(mSource.getType()),
mServerVisible, mClientVisible);
}
@@ -534,19 +601,24 @@ class InsetsSourceProvider {
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
// TODO(b/166736352): Check if we still need to control the IME visibility here.
if (mSource.getType() == ITYPE_IME) {
// TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
t.setAlpha(animationLeash, 1 /* alpha */);
t.hide(animationLeash);
}
- ProtoLog.i(WM_DEBUG_IME,
+ ProtoLog.i(WM_DEBUG_WINDOW_INSETS,
"ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource,
mControlTarget);
mCapturedLeash = animationLeash;
t.setPosition(mCapturedLeash, mSurfacePosition.x, mSurfacePosition.y);
+
+ if (mCropToProvidingInsets) {
+ // Apply crop to hide overflow
+ t.setWindowCrop(mCapturedLeash, getProvidingInsetsBoundsCropRect());
+ }
}
@Override
@@ -557,7 +629,7 @@ class InsetsSourceProvider {
mControlTarget = null;
mAdapter = null;
setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
- ProtoLog.i(WM_DEBUG_IME,
+ ProtoLog.i(WM_DEBUG_WINDOW_INSETS,
"ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
mSource, mControlTarget);
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 655007cf3cd1..c4ca8e364011 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -254,7 +254,7 @@ class InsetsStateController {
if (p == null) continue;
final WindowContainer wc = p.mWin;
if (wc == null) continue;
- mDisplayContent.mAtmService.getTransitionController().collect(wc);
+ mDisplayContent.mTransitionController.collect(wc);
}
}
@@ -385,7 +385,7 @@ class InsetsStateController {
if (changed) {
notifyInsetsChanged();
mDisplayContent.updateSystemGestureExclusion();
- mDisplayContent.getDisplayPolicy().updateSystemUiVisibilityLw();
+ mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 054854aef121..bd41de3a9509 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
@@ -27,6 +28,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_W
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
@@ -49,6 +51,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
+import android.view.WindowManager;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -158,6 +161,7 @@ class KeyguardController {
final boolean keyguardChanged = (keyguardShowing != mKeyguardShowing)
|| (mKeyguardGoingAway && keyguardShowing && !aodChanged);
if (!keyguardChanged && !aodChanged) {
+ setWakeTransitionReady();
return;
}
EventLogTags.writeWmSetKeyguardShown(
@@ -202,6 +206,15 @@ class KeyguardController {
updateKeyguardSleepToken();
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */);
+ setWakeTransitionReady();
+ }
+
+ private void setWakeTransitionReady() {
+ if (mWindowManager.mAtmService.getTransitionController().getCollectingTransitionType()
+ == WindowManager.TRANSIT_WAKE) {
+ mWindowManager.mAtmService.getTransitionController().setReady(
+ mRootWindowContainer.getDefaultDisplay());
+ }
}
/**
@@ -223,8 +236,14 @@ class KeyguardController {
mAodShowing ? 1 : 0,
1 /* keyguardGoingAway */,
"keyguardGoingAway");
- mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
- TRANSIT_KEYGUARD_GOING_AWAY, convertTransitFlags(flags));
+ final int transitFlags = convertTransitFlags(flags);
+ final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
+ dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
+ // We are deprecating TRANSIT_KEYGUARD_GOING_AWAY for Shell transition and use
+ // TRANSIT_FLAG_KEYGUARD_GOING_AWAY to indicate that it should animate keyguard going
+ // away.
+ dc.mAtmService.getTransitionController().requestTransitionIfNeeded(
+ TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
updateKeyguardSleepToken();
// Some stack visibility might change (e.g. docked stack)
@@ -264,7 +283,7 @@ class KeyguardController {
}
private int convertTransitFlags(int keyguardGoingAwayFlags) {
- int result = 0;
+ int result = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
if ((keyguardGoingAwayFlags & KEYGUARD_GOING_AWAY_FLAG_TO_SHADE) != 0) {
result |= TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
}
@@ -333,6 +352,7 @@ class KeyguardController {
for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
displayNdx >= 0; displayNdx--) {
final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
+ if (display.isRemoving() || display.isRemoved()) continue;
final KeyguardDisplayState state = getDisplayState(display.mDisplayId);
state.updateVisibility(this, display);
requestDismissKeyguard |= state.mRequestDismissKeyguard;
@@ -354,7 +374,7 @@ class KeyguardController {
// TODO(b/113840485): Handle app transition for individual display, and apply occluded
// state change to secondary displays.
// For now, only default display fully supports occluded change. Other displays only
- // updates keygaurd sleep token on that display.
+ // updates keyguard sleep token on that display.
if (displayId != DEFAULT_DISPLAY) {
updateKeyguardSleepToken(displayId);
return;
@@ -365,19 +385,10 @@ class KeyguardController {
mService.deferWindowLayout();
try {
mRootWindowContainer.getDefaultDisplay()
- .prepareAppTransition(
+ .requestTransitionAndLegacyPrepare(
isDisplayOccluded(DEFAULT_DISPLAY)
? TRANSIT_KEYGUARD_OCCLUDE
- : TRANSIT_KEYGUARD_UNOCCLUDE);
- // When the occluding activity also turns on the display, visibility of the activity
- // can be committed before KEYGUARD_OCCLUDE transition is handled.
- // Set mRequestForceTransition flag to make sure that the app transition animation
- // is applied for such case.
- // TODO(b/194243906): Fix this before enabling the remote keyguard animation.
- if (WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation
- && topActivity != null) {
- topActivity.mRequestForceTransition = true;
- }
+ : TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
updateKeyguardSleepToken(DEFAULT_DISPLAY);
mWindowManager.executeAppTransition();
} finally {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3dbe79df6722..4b98013a99cc 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -19,19 +19,24 @@ package com.android.server.wm;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.SurfaceControl.HIDDEN;
+import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Process;
+import android.view.GestureDetector;
import android.view.InputChannel;
+import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.server.UiThread;
+import java.util.function.IntConsumer;
import java.util.function.Supplier;
/**
@@ -58,11 +63,15 @@ public class Letterbox {
private final LetterboxSurface mLeft = new LetterboxSurface("left");
private final LetterboxSurface mBottom = new LetterboxSurface("bottom");
private final LetterboxSurface mRight = new LetterboxSurface("right");
- // Prevents wallpaper from peeking through near rounded corners. It's not included in
- // mSurfaces array since it isn't needed in methods like notIntersectsOrFullyContains
- // or attachInput.
- private final LetterboxSurface mBehind = new LetterboxSurface("behind");
+ // One surface that fills the whole window is used over multiple surfaces to:
+ // - Prevents wallpaper from peeking through near rounded corners.
+ // - For "blurred wallpaper" background, to avoid having visible border between surfaces.
+ // One surface approach isn't always preferred over multiple surfaces due to rendering cost
+ // for overlaping an app window and letterbox surfaces.
+ private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
+ // Reachability gestures.
+ private final IntConsumer mDoubleTapCallback;
/**
* Constructs a Letterbox.
@@ -75,7 +84,8 @@ public class Letterbox {
Supplier<Color> colorSupplier,
Supplier<Boolean> hasWallpaperBackgroundSupplier,
Supplier<Integer> blurRadiusSupplier,
- Supplier<Float> darkScrimAlphaSupplier) {
+ Supplier<Float> darkScrimAlphaSupplier,
+ IntConsumer doubleTapCallback) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAreCornersRounded = areCornersRounded;
@@ -83,6 +93,7 @@ public class Letterbox {
mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier;
mBlurRadiusSupplier = blurRadiusSupplier;
mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
+ mDoubleTapCallback = doubleTapCallback;
}
/**
@@ -104,7 +115,7 @@ public class Letterbox {
mLeft.layout(outer.left, outer.top, inner.left, outer.bottom, surfaceOrigin);
mBottom.layout(outer.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
mRight.layout(inner.right, outer.top, outer.right, outer.bottom, surfaceOrigin);
- mBehind.layout(inner.left, inner.top, inner.right, inner.bottom, surfaceOrigin);
+ mFullWindowSurface.layout(outer.left, outer.top, outer.right, outer.bottom, surfaceOrigin);
}
@@ -168,37 +179,46 @@ public class Letterbox {
for (LetterboxSurface surface : mSurfaces) {
surface.remove();
}
- mBehind.remove();
+ mFullWindowSurface.remove();
}
/** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */
public boolean needsApplySurfaceChanges() {
+ if (useFullWindowSurface()) {
+ return mFullWindowSurface.needsApplySurfaceChanges();
+ }
for (LetterboxSurface surface : mSurfaces) {
if (surface.needsApplySurfaceChanges()) {
return true;
}
}
- if (mAreCornersRounded.get() && mBehind.needsApplySurfaceChanges()) {
- return true;
- }
return false;
}
public void applySurfaceChanges(SurfaceControl.Transaction t) {
- for (LetterboxSurface surface : mSurfaces) {
- surface.applySurfaceChanges(t);
- }
- if (mAreCornersRounded.get()) {
- mBehind.applySurfaceChanges(t);
+ if (useFullWindowSurface()) {
+ mFullWindowSurface.applySurfaceChanges(t);
+
+ for (LetterboxSurface surface : mSurfaces) {
+ surface.remove();
+ }
} else {
- mBehind.remove();
+ for (LetterboxSurface surface : mSurfaces) {
+ surface.applySurfaceChanges(t);
+ }
+
+ mFullWindowSurface.remove();
}
}
/** Enables touches to slide into other neighboring surfaces. */
void attachInput(WindowState win) {
- for (LetterboxSurface surface : mSurfaces) {
- surface.attachInput(win);
+ if (useFullWindowSurface()) {
+ mFullWindowSurface.attachInput(win);
+ } else {
+ for (LetterboxSurface surface : mSurfaces) {
+ surface.attachInput(win);
+ }
}
}
@@ -208,20 +228,61 @@ public class Letterbox {
surface.mInputInterceptor.mWindowHandle.displayId = displayId;
}
}
+ if (mFullWindowSurface.mInputInterceptor != null) {
+ mFullWindowSurface.mInputInterceptor.mWindowHandle.displayId = displayId;
+ }
+ }
+
+ /**
+ * Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
+ */
+ private boolean useFullWindowSurface() {
+ return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
+ }
+
+ private final class TapEventReceiver extends InputEventReceiver {
+
+ private final GestureDetector mDoubleTapDetector;
+ private final DoubleTapListener mDoubleTapListener;
+
+ TapEventReceiver(InputChannel inputChannel, Context context) {
+ super(inputChannel, UiThread.getHandler().getLooper());
+ mDoubleTapListener = new DoubleTapListener();
+ mDoubleTapDetector = new GestureDetector(
+ context, mDoubleTapListener, UiThread.getHandler());
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ final MotionEvent motionEvent = (MotionEvent) event;
+ finishInputEvent(event, mDoubleTapDetector.onTouchEvent(motionEvent));
+ }
+ }
+
+ private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ if (e.getAction() == MotionEvent.ACTION_UP) {
+ mDoubleTapCallback.accept((int) e.getX());
+ return true;
+ }
+ return false;
+ }
}
- private static class InputInterceptor {
- final InputChannel mClientChannel;
- final InputWindowHandle mWindowHandle;
- final InputEventReceiver mInputEventReceiver;
- final WindowManagerService mWmService;
- final IBinder mToken;
+ private final class InputInterceptor {
+
+ private final InputChannel mClientChannel;
+ private final InputWindowHandle mWindowHandle;
+ private final InputEventReceiver mInputEventReceiver;
+ private final WindowManagerService mWmService;
+ private final IBinder mToken;
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
mClientChannel = mWmService.mInputManager.createInputChannel(name);
- mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
+ mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext);
mToken = mClientChannel.getToken();
@@ -259,12 +320,6 @@ public class Letterbox {
mInputEventReceiver.dispose();
mClientChannel.dispose();
}
-
- private static class SimpleInputReceiver extends InputEventReceiver {
- SimpleInputReceiver(InputChannel inputChannel) {
- super(inputChannel, UiThread.getHandler().getLooper());
- }
- }
}
private class LetterboxSurface {
@@ -308,6 +363,10 @@ public class Letterbox {
mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win);
}
+ boolean isRemoved() {
+ return mSurface != null || mInputInterceptor != null;
+ }
+
public void remove() {
if (mSurface != null) {
mTransactionFactory.get().remove(mSurface).apply();
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 7174e68b06f4..e8490c5873ef 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -17,11 +17,11 @@
package com.android.server.wm;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -30,8 +30,7 @@ import java.lang.annotation.RetentionPolicy;
final class LetterboxConfiguration {
/**
- * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
- * set-fixed-orientation-letterbox-aspect-ratio or via {@link
+ * Override of aspect ratio for fixed orientation letterboxing that is set via {@link
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored
* if it is <= this value.
*/
@@ -54,6 +53,27 @@ final class LetterboxConfiguration {
/** Using wallpaper as a background which can be blurred or dimmed with dark scrim. */
static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
+ /**
+ * Enum for Letterbox reachability position types.
+ *
+ * <p>Order from left to right is important since it's used in {@link
+ * #movePositionForReachabilityToNextRightStop} and {@link
+ * #movePositionForReachabilityToNextLeftStop}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({LETTERBOX_REACHABILITY_POSITION_LEFT, LETTERBOX_REACHABILITY_POSITION_CENTER,
+ LETTERBOX_REACHABILITY_POSITION_RIGHT})
+ @interface LetterboxReachabilityPosition {};
+
+ /** Letterboxed app window is aligned to the left side. */
+ static final int LETTERBOX_REACHABILITY_POSITION_LEFT = 0;
+
+ /** Letterboxed app window is positioned in the horizontal center. */
+ static final int LETTERBOX_REACHABILITY_POSITION_CENTER = 1;
+
+ /** Letterboxed app window is aligned to the right side. */
+ static final int LETTERBOX_REACHABILITY_POSITION_RIGHT = 2;
+
final Context mContext;
// Aspect ratio of letterbox for fixed orientation, values <=
@@ -64,7 +84,10 @@ final class LetterboxConfiguration {
private int mLetterboxActivityCornersRadius;
// Color for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type.
- private Color mLetterboxBackgroundColor;
+ @Nullable private Color mLetterboxBackgroundColorOverride;
+
+ // Color resource id for {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} letterbox background type.
+ @Nullable private Integer mLetterboxBackgroundColorResourceIdOverride;
@LetterboxBackgroundType
private int mLetterboxBackgroundType;
@@ -82,21 +105,43 @@ final class LetterboxConfiguration {
// side of the screen and 1.0 to the right side.
private float mLetterboxHorizontalPositionMultiplier;
- LetterboxConfiguration(Context context) {
- mContext = context;
- mFixedOrientationLetterboxAspectRatio = context.getResources().getFloat(
+ // Default horizontal position the letterboxed app window when reachability is enabled and
+ // an app is fullscreen in landscape device orientatio.
+ // It is used as a starting point for mLetterboxPositionForReachability.
+ @LetterboxReachabilityPosition
+ private int mDefaultPositionForReachability;
+
+ // Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
+ // device orientation.
+ private boolean mIsReachabilityEnabled;
+
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsHorizontalPosition.
+ // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
+ // Overview after changing position in another app.
+ @LetterboxReachabilityPosition
+ private volatile int mLetterboxPositionForReachability;
+
+ LetterboxConfiguration(Context systemUiContext) {
+ mContext = systemUiContext;
+ mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
- mLetterboxActivityCornersRadius = context.getResources().getInteger(
+ mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
R.integer.config_letterboxActivityCornersRadius);
- mLetterboxBackgroundColor = Color.valueOf(context.getResources().getColor(
- R.color.config_letterboxBackgroundColor));
- mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(context);
- mLetterboxBackgroundWallpaperBlurRadius = context.getResources().getDimensionPixelSize(
+ mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+ mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
- mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat(
+ mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
- mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat(
+ mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
R.dimen.config_letterboxHorizontalPositionMultiplier);
+ mIsReachabilityEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsReachabilityEnabled);
+ mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+ mLetterboxPositionForReachability = mDefaultPositionForReachability;
}
/**
@@ -105,12 +150,20 @@ final class LetterboxConfiguration {
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
* the framework implementation will be used to determine the aspect ratio.
*/
- @VisibleForTesting
void setFixedOrientationLetterboxAspectRatio(float aspectRatio) {
mFixedOrientationLetterboxAspectRatio = aspectRatio;
}
/**
+ * Resets the aspect ratio of letterbox for fixed orientation to {@link
+ * com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}.
+ */
+ void resetFixedOrientationLetterboxAspectRatio() {
+ mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio);
+ }
+
+ /**
* Gets the aspect ratio of letterbox for fixed orientation.
*/
float getFixedOrientationLetterboxAspectRatio() {
@@ -118,10 +171,29 @@ final class LetterboxConfiguration {
}
/**
+ * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+ * both it and a value of {@link
+ * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
+ * corners of the activity won't be rounded.
+ */
+ void setLetterboxActivityCornersRadius(int cornersRadius) {
+ mLetterboxActivityCornersRadius = cornersRadius;
+ }
+
+ /**
+ * Resets corners raidus for activities presented in the letterbox mode to {@link
+ * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
+ */
+ void resetLetterboxActivityCornersRadius() {
+ mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+ }
+
+ /**
* Whether corners of letterboxed activities are rounded.
*/
boolean isLetterboxActivityCornersRounded() {
- return getLetterboxActivityCornersRadius() > 0;
+ return getLetterboxActivityCornersRadius() != 0;
}
/**
@@ -132,23 +204,72 @@ final class LetterboxConfiguration {
}
/**
- * Gets color of letterbox background which is used when {@link
+ * Gets color of letterbox background which is used when {@link
* #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
* fallback for other backfround types.
*/
Color getLetterboxBackgroundColor() {
- return mLetterboxBackgroundColor;
+ if (mLetterboxBackgroundColorOverride != null) {
+ return mLetterboxBackgroundColorOverride;
+ }
+ int colorId = mLetterboxBackgroundColorResourceIdOverride != null
+ ? mLetterboxBackgroundColorResourceIdOverride
+ : R.color.config_letterboxBackgroundColor;
+ // Query color dynamically because material colors extracted from wallpaper are updated
+ // when wallpaper is changed.
+ return Color.valueOf(mContext.getResources().getColor(colorId));
+ }
+
+
+ /**
+ * Sets color of letterbox background which is used when {@link
+ * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+ * fallback for other backfround types.
+ */
+ void setLetterboxBackgroundColor(Color color) {
+ mLetterboxBackgroundColorOverride = color;
+ }
+
+ /**
+ * Sets color ID of letterbox background which is used when {@link
+ * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+ * fallback for other backfround types.
+ */
+ void setLetterboxBackgroundColorResourceId(int colorId) {
+ mLetterboxBackgroundColorResourceIdOverride = colorId;
+ }
+
+ /**
+ * Resets color of letterbox background to {@link
+ * com.android.internal.R.color.config_letterboxBackgroundColor}.
+ */
+ void resetLetterboxBackgroundColor() {
+ mLetterboxBackgroundColorOverride = null;
+ mLetterboxBackgroundColorResourceIdOverride = null;
}
/**
* Gets {@link LetterboxBackgroundType} specified in {@link
- * com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
+ * com.android.internal.R.integer.config_letterboxBackgroundType}.
*/
@LetterboxBackgroundType
int getLetterboxBackgroundType() {
return mLetterboxBackgroundType;
}
+ /** Sets letterbox background type. */
+ void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+ mLetterboxBackgroundType = backgroundType;
+ }
+
+ /**
+ * Resets cletterbox background type to {@link
+ * com.android.internal.R.integer.config_letterboxBackgroundType}.
+ */
+ void resetLetterboxBackgroundType() {
+ mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+ }
+
/** Returns a string representing the given {@link LetterboxBackgroundType}. */
static String letterboxBackgroundTypeToString(
@LetterboxBackgroundType int backgroundType) {
@@ -178,6 +299,27 @@ final class LetterboxConfiguration {
}
/**
+ * Overrides alpha of a black scrim shown over wallpaper for {@link
+ * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+ *
+ * <p>If given value is < 0 or >= 1, both it and a value of {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
+ * and 0.0 (transparent) is instead.
+ */
+ void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) {
+ mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha;
+ }
+
+ /**
+ * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}.
+ */
+ void resetLetterboxBackgroundWallpaperDarkScrimAlpha() {
+ mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+ }
+
+ /**
* Gets alpha of a black scrim shown over wallpaper letterbox background.
*/
float getLetterboxBackgroundWallpaperDarkScrimAlpha() {
@@ -185,6 +327,28 @@ final class LetterboxConfiguration {
}
/**
+ * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
+ * {@link mLetterboxBackgroundType}.
+ *
+ * <p> If given value <= 0, both it and a value of {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
+ * and 0 is used instead.
+ */
+ void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
+ mLetterboxBackgroundWallpaperBlurRadius = radius;
+ }
+
+ /**
+ * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
+ * mLetterboxBackgroundType} to {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
+ */
+ void resetLetterboxBackgroundWallpaperBlurRadius() {
+ mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
+ }
+
+ /**
* Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
* mLetterboxBackgroundType}.
*/
@@ -194,15 +358,14 @@ final class LetterboxConfiguration {
/*
* Gets horizontal position of a center of the letterboxed app window specified
- * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}
- * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
- * right side.
- *
- * <p>This value can be outside of [0, 1] range so clients need to check and default to the
- * central position (0.5).
+ * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
+ * 0 corresponds to the left side of the screen and 1 to the right side.
*/
float getLetterboxHorizontalPositionMultiplier() {
- return mLetterboxHorizontalPositionMultiplier;
+ return (mLetterboxHorizontalPositionMultiplier < 0.0f
+ || mLetterboxHorizontalPositionMultiplier > 1.0f)
+ // Default to central position if invalid value is provided.
+ ? 0.5f : mLetterboxHorizontalPositionMultiplier;
}
/**
@@ -211,9 +374,128 @@ final class LetterboxConfiguration {
* com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
* central position (0.5) is used.
*/
- @VisibleForTesting
void setLetterboxHorizontalPositionMultiplier(float multiplier) {
mLetterboxHorizontalPositionMultiplier = multiplier;
}
+ /**
+ * Resets horizontal position of a center of the letterboxed app window to {@link
+ * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
+ */
+ void resetLetterboxHorizontalPositionMultiplier() {
+ mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
+ }
+
+ /*
+ * Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
+ * device orientation.
+ */
+ boolean getIsReachabilityEnabled() {
+ return mIsReachabilityEnabled;
+ }
+
+ /**
+ * Overrides whether reachability repositioning is allowed for letterboxed fullscreen apps in
+ * landscape device orientation.
+ */
+ void setIsReachabilityEnabled(boolean enabled) {
+ mIsReachabilityEnabled = enabled;
+ }
+
+ /**
+ * Resets whether reachability repositioning is allowed for letterboxed fullscreen apps in
+ * landscape device orientation to {@link R.bool.config_letterboxIsReachabilityEnabled}.
+ */
+ void resetIsReachabilityEnabled() {
+ mIsReachabilityEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsReachabilityEnabled);
+ }
+
+ /*
+ * Gets default horizontal position of the letterboxed app window when reachability is enabled.
+ * Specified in {@link R.integer.config_letterboxDefaultPositionForReachability}.
+ */
+ @LetterboxReachabilityPosition
+ int getDefaultPositionForReachability() {
+ return mDefaultPositionForReachability;
+ }
+
+ /**
+ * Overrides default horizonal position of the letterboxed app window when reachability
+ * is enabled.
+ */
+ void setDefaultPositionForReachability(@LetterboxReachabilityPosition int position) {
+ mDefaultPositionForReachability = position;
+ }
+
+ /**
+ * Resets default horizontal position of the letterboxed app window when reachability is
+ * enabled to {@link R.integer.config_letterboxDefaultPositionForReachability}.
+ */
+ void resetDefaultPositionForReachability() {
+ mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
+ }
+
+ @LetterboxReachabilityPosition
+ private static int readLetterboxReachabilityPositionFromConfig(Context context) {
+ int position = context.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForReachability);
+ return position == LETTERBOX_REACHABILITY_POSITION_LEFT
+ || position == LETTERBOX_REACHABILITY_POSITION_CENTER
+ || position == LETTERBOX_REACHABILITY_POSITION_RIGHT
+ ? position : LETTERBOX_REACHABILITY_POSITION_CENTER;
+ }
+
+ /*
+ * Gets horizontal position of a center of the letterboxed app window when reachability
+ * is enabled specified. 0 corresponds to the left side of the screen and 1 to the right side.
+ *
+ * <p>The position multiplier is changed after each double tap in the letterbox area.
+ */
+ float getHorizontalMultiplierForReachability() {
+ switch (mLetterboxPositionForReachability) {
+ case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ return 0.0f;
+ case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ return 0.5f;
+ case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ return 1.0f;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + mLetterboxPositionForReachability);
+ }
+ }
+
+ /** Returns a string representing the given {@link LetterboxReachabilityPosition}. */
+ static String letterboxReachabilityPositionToString(
+ @LetterboxReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ return "LETTERBOX_REACHABILITY_POSITION_LEFT";
+ case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ return "LETTERBOX_REACHABILITY_POSITION_CENTER";
+ case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ return "LETTERBOX_REACHABILITY_POSITION_RIGHT";
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + position);
+ }
+ }
+
+ /**
+ * Changes letterbox position for reachability to the next available one on the right side.
+ */
+ void movePositionForReachabilityToNextRightStop() {
+ mLetterboxPositionForReachability = Math.min(
+ mLetterboxPositionForReachability + 1, LETTERBOX_REACHABILITY_POSITION_RIGHT);
+ }
+
+ /**
+ * Changes letterbox position for reachability to the next available one on the left side.
+ */
+ void movePositionForReachabilityToNextLeftStop() {
+ mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index b6b8ad14e106..8866343afe03 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -16,8 +16,12 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static com.android.server.wm.ActivityRecord.computeAspectRatio;
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.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
@@ -28,14 +32,20 @@ import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTy
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Slog;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
@@ -55,6 +65,10 @@ final class LetterboxUiController {
private final LetterboxConfiguration mLetterboxConfiguration;
private final ActivityRecord mActivityRecord;
+ // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
+ // corners above the taskbar.
+ private float mExpandedTaskBarHeight;
+
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
@@ -66,6 +80,8 @@ final class LetterboxUiController {
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
+ mExpandedTaskBarHeight =
+ getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -134,11 +150,12 @@ final class LetterboxUiController {
if (mLetterbox == null) {
mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
mActivityRecord.mWmService.mTransactionFactory,
- mLetterboxConfiguration::isLetterboxActivityCornersRounded,
+ this::shouldLetterboxHaveRoundedCorners,
this::getLetterboxBackgroundColor,
this::hasWallpaperBackgroudForLetterbox,
this::getLetterboxWallpaperBlurRadius,
- this::getLetterboxWallpaperDarkScrimAlpha);
+ this::getLetterboxWallpaperDarkScrimAlpha,
+ this::handleDoubleTap);
mLetterbox.attachInput(w);
}
mActivityRecord.getPosition(mTmpPoint);
@@ -158,11 +175,95 @@ final class LetterboxUiController {
}
}
+ private boolean shouldLetterboxHaveRoundedCorners() {
+ // TODO(b/214030873): remove once background is drawn for transparent activities
+ // Letterbox shouldn't have rounded corners if the activity is transparent
+ return mLetterboxConfiguration.isLetterboxActivityCornersRounded()
+ && mActivityRecord.fillsParent();
+ }
+
+ float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
+ // Don't check resolved configuration because it may not be updated yet during
+ // configuration change.
+ return isReachabilityEnabled(parentConfiguration)
+ // Using the last global dynamic position to avoid "jumps" when moving
+ // between apps or activities.
+ ? mLetterboxConfiguration.getHorizontalMultiplierForReachability()
+ : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
+ }
+
+ float getFixedOrientationLetterboxAspectRatio(Configuration parentConfiguration) {
+ // Don't check resolved windowing mode because it may not be updated yet during
+ // configuration change.
+ if (!isReachabilityEnabled(parentConfiguration)) {
+ return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ }
+
+ int dividerWindowWidth =
+ getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
+ int dividerInsets =
+ getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
+ int dividerSize = dividerWindowWidth - dividerInsets * 2;
+
+ // Getting the same aspect ratio that apps get in split screen.
+ Rect bounds = new Rect(parentConfiguration.windowConfiguration.getAppBounds());
+ bounds.inset(dividerSize, /* dy */ 0);
+ bounds.right = bounds.centerX();
+
+ return computeAspectRatio(bounds);
+ }
+
+ Resources getResources() {
+ return mActivityRecord.mWmService.mContext.getResources();
+ }
+
+ private void handleDoubleTap(int x) {
+ if (!isReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ return;
+ }
+
+ if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) {
+ // Only react to clicks at the sides of the letterboxed app window.
+ return;
+ }
+
+ if (mLetterbox.getInnerFrame().left > x) {
+ // Moving to the next stop on the left side of the app window: right > center > left.
+ mLetterboxConfiguration.movePositionForReachabilityToNextLeftStop();
+ } else if (mLetterbox.getInnerFrame().right < x) {
+ // Moving to the next stop on the right side of the app window: left > center > right.
+ mLetterboxConfiguration.movePositionForReachabilityToNextRightStop();
+ }
+
+ // TODO(197549949): Add animation for transition.
+ mActivityRecord.recomputeConfiguration();
+ }
+
+ /**
+ * Whether reachability is enabled for an activity in the curren configuration.
+ *
+ * <p>Conditions that needs to be met:
+ * <ul>
+ * <li>Activity is portrait-only.
+ * <li>Fullscreen window in landscape device orientation.
+ * <li>Reachability is enabled.
+ * </ul>
+ */
+ private boolean isReachabilityEnabled(Configuration parentConfiguration) {
+ return mLetterboxConfiguration.getIsReachabilityEnabled()
+ && parentConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN
+ && parentConfiguration.orientation == ORIENTATION_LANDSCAPE
+ && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+ }
+
+ private boolean isReachabilityEnabled() {
+ return isReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ }
+
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
- // Check that an activity isn't transparent.
- && mActivityRecord.fillsParent()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox backgroud.
@@ -219,21 +320,72 @@ final class LetterboxUiController {
}
private void updateRoundedCorners(WindowState mainWindow) {
- int cornersRadius =
- // Don't round corners if letterboxed only for display cutout.
- shouldShowLetterboxUi(mainWindow)
- && !mainWindow.isLetterboxedForDisplayCutout()
- ? Math.max(0, mLetterboxConfiguration.getLetterboxActivityCornersRadius())
- : 0;
- setCornersRadius(mainWindow, cornersRadius);
- }
-
- private void setCornersRadius(WindowState mainWindow, int cornersRadius) {
final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface();
if (windowSurface != null && windowSurface.isValid()) {
Transaction transaction = mActivityRecord.getSyncTransaction();
- transaction.setCornerRadius(windowSurface, cornersRadius);
+
+ if (!isLetterboxedNotForDisplayCutout(mainWindow)
+ || !mLetterboxConfiguration.isLetterboxActivityCornersRounded()) {
+ transaction
+ .setWindowCrop(windowSurface, null)
+ .setCornerRadius(windowSurface, 0);
+ return;
+ }
+
+ final InsetsState insetsState = mainWindow.getInsetsState();
+ final InsetsSource taskbarInsetsSource =
+ insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+
+ Rect cropBounds = null;
+
+ // Rounded corners should be displayed above the taskbar. When taskbar is hidden,
+ // an insets frame is equal to a navigation bar which shouldn't affect position of
+ // rounded corners since apps are expected to handle navigation bar inset.
+ // This condition checks whether the taskbar is visible.
+ if (taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ cropBounds = new Rect(mActivityRecord.getBounds());
+ // Activity bounds are in screen coordinates while (0,0) for activity's surface
+ // control is at the top left corner of an app window so offsetting bounds
+ // accordingly.
+ cropBounds.offsetTo(0, 0);
+ // Rounded cornerners should be displayed above the taskbar.
+ cropBounds.bottom =
+ Math.min(cropBounds.bottom, taskbarInsetsSource.getFrame().top);
+ if (mActivityRecord.inSizeCompatMode()
+ && mActivityRecord.getSizeCompatScale() < 1.0f) {
+ cropBounds.scale(1.0f / mActivityRecord.getSizeCompatScale());
+ }
+ }
+
+ transaction
+ .setWindowCrop(windowSurface, cropBounds)
+ .setCornerRadius(windowSurface, getRoundedCorners(insetsState));
+ }
+ }
+
+ // Returns rounded corners radius based on override in
+ // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
+ // Device corners can be different on the right and left sides but we use the same radius
+ // for all corners for consistency and pick a minimal bottom one for consistency with a
+ // taskbar rounded corners.
+ private int getRoundedCorners(InsetsState insetsState) {
+ if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) {
+ return mLetterboxConfiguration.getLetterboxActivityCornersRadius();
}
+ return Math.min(
+ getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
+ getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
+ }
+
+ private int getInsetsStateCornerRadius(
+ InsetsState insetsState, @RoundedCorner.Position int position) {
+ RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position);
+ return corner == null ? 0 : corner.getRadius();
+ }
+
+ private boolean isLetterboxedNotForDisplayCutout(WindowState mainWindow) {
+ return shouldShowLetterboxUi(mainWindow)
+ && !mainWindow.isLetterboxedForDisplayCutout();
}
private void updateWallpaperForLetterbox(WindowState mainWindow) {
@@ -241,9 +393,8 @@ final class LetterboxUiController {
mLetterboxConfiguration.getLetterboxBackgroundType();
boolean wallpaperShouldBeShown =
letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER
- && shouldShowLetterboxUi(mainWindow)
// Don't use wallpaper as a background if letterboxed for display cutout.
- && !mainWindow.isLetterboxedForDisplayCutout()
+ && isLetterboxedNotForDisplayCutout(mainWindow)
// Check that dark scrim alpha or blur radius are provided
&& (getLetterboxWallpaperBlurRadius() > 0
|| getLetterboxWallpaperDarkScrimAlpha() > 0)
@@ -285,7 +436,7 @@ final class LetterboxUiController {
}
pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin));
- pw.println(prefix + " letterboxAspectRatio="
+ pw.println(prefix + " activityAspectRatio="
+ mActivityRecord.computeAspectRatio(mActivityRecord.getBounds()));
boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin);
@@ -299,6 +450,8 @@ final class LetterboxUiController {
pw.println(prefix + " letterboxBackgroundType="
+ letterboxBackgroundTypeToString(
mLetterboxConfiguration.getLetterboxBackgroundType()));
+ pw.println(prefix + " letterboxCornerRadius="
+ + getRoundedCorners(mainWin.getInsetsState()));
if (mLetterboxConfiguration.getLetterboxBackgroundType()
== LETTERBOX_BACKGROUND_WALLPAPER) {
pw.println(prefix + " isLetterboxWallpaperBlurSupported="
@@ -308,8 +461,13 @@ final class LetterboxUiController {
pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius="
+ getLetterboxWallpaperBlurRadius());
}
+
+ pw.println(prefix + " isReachabilityEnabled=" + isReachabilityEnabled());
pw.println(prefix + " letterboxHorizontalPositionMultiplier="
- + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+ + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ pw.println(prefix + " fixedOrientationLetterboxAspectRatio="
+ + getFixedOrientationLetterboxAspectRatio(
+ mActivityRecord.getParent().getConfiguration()));
}
/**
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 520bd8b2108e..a3eb980992c7 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static com.android.server.wm.AnimationAdapterProto.LOCAL;
import static com.android.server.wm.LocalAnimationAdapterProto.ANIMATION_SPEC;
+import android.annotation.NonNull;
import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
@@ -51,7 +52,7 @@ class LocalAnimationAdapter implements AnimationAdapter {
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
mAnimator.startAnimation(mSpec, animationLeash, t,
() -> finishCallback.onAnimationFinished(type, this));
}
diff --git a/services/core/java/com/android/server/wm/LocaleOverlayHelper.java b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
new file mode 100644
index 000000000000..a1a01dba769a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LocaleOverlayHelper.java
@@ -0,0 +1,62 @@
+/*
+ * 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.wm;
+
+import android.os.LocaleList;
+
+import java.util.Locale;
+
+/**
+ * Static utilities to overlay locales on top of another localeList.
+ *
+ * <p>This is used to overlay application-specific locales in
+ * {@link com.android.server.wm.ActivityTaskManagerInternal.PackageConfigurationUpdater} on top of
+ * system locales.
+ */
+final class LocaleOverlayHelper {
+
+ /**
+ * Combines the overlay locales and base locales.
+ * @return the combined {@link LocaleList} if the overlay locales is not empty/null else
+ * returns the empty/null LocaleList.
+ */
+ static LocaleList combineLocalesIfOverlayExists(LocaleList overlayLocales,
+ LocaleList baseLocales) {
+ if (overlayLocales == null || overlayLocales.isEmpty()) {
+ return overlayLocales;
+ }
+ return combineLocales(overlayLocales, baseLocales);
+ }
+
+ /**
+ * Creates a combined {@link LocaleList} by placing overlay locales before base locales and
+ * dropping duplicates from the base locales.
+ */
+ private static LocaleList combineLocales(LocaleList overlayLocales, LocaleList baseLocales) {
+ Locale[] combinedLocales = new Locale[overlayLocales.size() + baseLocales.size()];
+ for (int i = 0; i < overlayLocales.size(); i++) {
+ combinedLocales[i] = overlayLocales.get(i);
+ }
+ for (int i = 0; i < baseLocales.size(); i++) {
+ combinedLocales[i + overlayLocales.size()] = baseLocales.get(i);
+ }
+ // Constructor of {@link LocaleList} removes duplicates
+ return new LocaleList(combinedLocales);
+ }
+
+
+}
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 94a175caba22..8a2d11636fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -251,15 +251,47 @@ public class LockTaskController {
*/
boolean activityBlockedFromFinish(ActivityRecord activity) {
final Task task = activity.getTask();
- if (activity == task.getRootActivity()
- && activity == task.getTopNonFinishingActivity()
- && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
- && isRootTask(task)) {
- Slog.i(TAG, "Not finishing task in lock task mode");
- showLockTaskToast();
- return true;
+ if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV || !isRootTask(task)) {
+ return false;
}
- return false;
+
+ final ActivityRecord taskTop = task.getTopNonFinishingActivity();
+ final ActivityRecord taskRoot = task.getRootActivity();
+ // If task has more than one Activity, verify if there's only adjacent TaskFragments that
+ // should be finish together in the Task.
+ if (activity != taskRoot || activity != taskTop) {
+ final TaskFragment taskFragment = activity.getTaskFragment();
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (taskFragment.asTask() != null
+ || !taskFragment.isDelayLastActivityRemoval()
+ || adjacentTaskFragment == null) {
+ // Don't block activity from finishing if the TaskFragment don't have any adjacent
+ // TaskFragment, or it won't finish together with its adjacent TaskFragment.
+ return false;
+ }
+
+ final boolean hasOtherActivityInTaskFragment =
+ taskFragment.getActivity(a -> !a.finishing && a != activity) != null;
+ if (hasOtherActivityInTaskFragment) {
+ // Don't block activity from finishing if there's other Activity in the same
+ // TaskFragment.
+ return false;
+ }
+
+ final boolean hasOtherActivityInTask = task.getActivity(a -> !a.finishing
+ && a != activity && a.getTaskFragment() != adjacentTaskFragment) != null;
+ if (hasOtherActivityInTask) {
+ // Do not block activity from finishing if there are another running activities
+ // after the current and adjacent TaskFragments are removed. Note that we don't
+ // check activities in adjacent TaskFragment because it will be finished together
+ // with TaskFragment regardless of numbers of activities.
+ return false;
+ }
+ }
+
+ Slog.i(TAG, "Not finishing task in lock task mode");
+ showLockTaskToast();
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index e50dc51c402d..7abf3b820c18 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import android.annotation.NonNull;
import android.view.SurfaceControl;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -144,7 +145,7 @@ public class NavBarFadeAnimationController extends FadeAnimationController{
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
super.startAnimation(animationLeash, t, type, finishCallback);
if (mParent != null && mParent.isValid()) {
t.reparent(animationLeash, mParent);
diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
index d230936b5d51..7c35a2163d6d 100644
--- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java
@@ -27,6 +27,7 @@ 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;
+import android.annotation.NonNull;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;
@@ -68,25 +69,32 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter {
long durationHint, long statusBarTransitionDelay,
ArrayList<NonAppWindowAnimationAdapter> adaptersOut) {
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
+ if (shouldStartNonAppWindowAnimationsForKeyguardExit(transit)) {
startNonAppWindowAnimationsForKeyguardExit(
service, durationHint, statusBarTransitionDelay, targets, adaptersOut);
- } else if (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
- || transit == TRANSIT_OLD_WALLPAPER_CLOSE) {
- final boolean shouldAttachNavBarToApp =
- displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
- && service.getRecentsAnimationController() == null
- && displayContent.getFadeRotationAnimationController() == null;
- if (shouldAttachNavBarToApp) {
- startNavigationBarWindowAnimation(
- displayContent, durationHint, statusBarTransitionDelay, targets,
- adaptersOut);
- }
+ } else if (shouldAttachNavBarToApp(service, displayContent, transit)) {
+ startNavigationBarWindowAnimation(
+ displayContent, durationHint, statusBarTransitionDelay, targets,
+ adaptersOut);
}
return targets.toArray(new RemoteAnimationTarget[targets.size()]);
}
+ static boolean shouldStartNonAppWindowAnimationsForKeyguardExit(
+ @WindowManager.TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
+ || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
+ }
+
+ static boolean shouldAttachNavBarToApp(WindowManagerService service,
+ DisplayContent displayContent, @WindowManager.TransitionOldType int transit) {
+ return (transit == TRANSIT_OLD_TASK_OPEN || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_WALLPAPER_CLOSE)
+ && displayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
+ && service.getRecentsAnimationController() == null
+ && displayContent.getFadeRotationAnimationController() == null;
+ }
+
/**
* Creates and starts remote animations for all the visible non app windows.
*
@@ -138,14 +146,14 @@ class NonAppWindowAnimationAdapter implements AnimationAdapter {
mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false,
new Rect(), null, mWindowContainer.getPrefixOrderIndex(),
mWindowContainer.getLastSurfacePosition(), mWindowContainer.getBounds(), null,
- mWindowContainer.getWindowConfiguration(), true, null, null, null,
+ mWindowContainer.getWindowConfiguration(), true, null, null, null, false,
mWindowContainer.getWindowType());
return mTarget;
}
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
mCapturedLeash = animationLeash;
mCapturedLeashFinishCallback = finishCallback;
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 1552a96d699a..fe21e5f0011e 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -16,11 +16,10 @@
package com.android.server.wm;
-import static android.app.UiModeManager.MODE_NIGHT_AUTO;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
-
import android.annotation.NonNull;
+import android.content.res.Configuration;
import android.os.Environment;
+import android.os.LocaleList;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -54,12 +53,14 @@ public class PackageConfigPersister {
private static final String TAG_CONFIG = "config";
private static final String ATTR_PACKAGE_NAME = "package_name";
private static final String ATTR_NIGHT_MODE = "night_mode";
+ private static final String ATTR_LOCALES = "locale_list";
private static final String PACKAGE_DIRNAME = "package_configs";
private static final String SUFFIX_FILE_NAME = "_config.xml";
private final PersisterQueue mPersisterQueue;
private final Object mLock = new Object();
+ private final ActivityTaskManagerService mAtm;
@GuardedBy("mLock")
private final SparseArray<HashMap<String, PackageConfigRecord>> mPendingWrite =
@@ -72,8 +73,9 @@ public class PackageConfigPersister {
return new File(Environment.getDataSystemCeDirectory(userId), PACKAGE_DIRNAME);
}
- PackageConfigPersister(PersisterQueue queue) {
+ PackageConfigPersister(PersisterQueue queue, ActivityTaskManagerService atm) {
mPersisterQueue = queue;
+ mAtm = atm;
}
@GuardedBy("mLock")
@@ -100,7 +102,8 @@ public class PackageConfigPersister {
final TypedXmlPullParser in = Xml.resolvePullParser(is);
int event;
String packageName = null;
- int nightMode = MODE_NIGHT_AUTO;
+ Integer nightMode = null;
+ LocaleList locales = null;
while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
&& event != XmlPullParser.END_TAG) {
final String name = in.getName();
@@ -120,6 +123,9 @@ public class PackageConfigPersister {
case ATTR_NIGHT_MODE:
nightMode = Integer.parseInt(attrValue);
break;
+ case ATTR_LOCALES:
+ locales = LocaleList.forLanguageTags(attrValue);
+ break;
}
}
}
@@ -130,6 +136,7 @@ public class PackageConfigPersister {
final PackageConfigRecord initRecord =
findRecordOrCreate(mModified, packageName, userId);
initRecord.mNightMode = nightMode;
+ initRecord.mLocales = locales;
if (DEBUG) {
Slog.d(TAG, "loadPackages: load one package " + initRecord);
}
@@ -155,20 +162,28 @@ public class PackageConfigPersister {
"updateConfigIfNeeded record " + container + " find? " + modifiedRecord);
}
if (modifiedRecord != null) {
- container.setOverrideNightMode(modifiedRecord.mNightMode);
+ container.applyAppSpecificConfig(modifiedRecord.mNightMode,
+ LocaleOverlayHelper.combineLocalesIfOverlayExists(
+ modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
}
}
}
@GuardedBy("mLock")
void updateFromImpl(String packageName, int userId,
- ActivityTaskManagerService.PackageConfigurationUpdaterImpl impl) {
+ PackageConfigurationUpdaterImpl impl) {
synchronized (mLock) {
PackageConfigRecord record = findRecordOrCreate(mModified, packageName, userId);
- record.mNightMode = impl.getNightMode();
-
- if (record.isResetNightMode()) {
- removePackage(record.mName, record.mUserId);
+ if (impl.getNightMode() != null) {
+ record.mNightMode = impl.getNightMode();
+ }
+ if (impl.getLocales() != null) {
+ record.mLocales = impl.getLocales();
+ }
+ if ((record.mNightMode == null || record.isResetNightMode())
+ && (record.mLocales == null || record.mLocales.isEmpty())) {
+ // if all values default to system settings, we can remove the package.
+ removePackage(packageName, userId);
} else {
final PackageConfigRecord pendingRecord =
findRecord(mPendingWrite, record.mName, record.mUserId);
@@ -179,10 +194,11 @@ public class PackageConfigPersister {
} else {
writeRecord = pendingRecord;
}
- if (writeRecord.mNightMode == record.mNightMode) {
+
+ if (!updateNightMode(record, writeRecord) && !updateLocales(record, writeRecord)) {
return;
}
- writeRecord.mNightMode = record.mNightMode;
+
if (DEBUG) {
Slog.d(TAG, "PackageConfigUpdater save config " + writeRecord);
}
@@ -191,6 +207,22 @@ public class PackageConfigPersister {
}
}
+ private boolean updateNightMode(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+ if (record.mNightMode == null || record.mNightMode.equals(writeRecord.mNightMode)) {
+ return false;
+ }
+ writeRecord.mNightMode = record.mNightMode;
+ return true;
+ }
+
+ private boolean updateLocales(PackageConfigRecord record, PackageConfigRecord writeRecord) {
+ if (record.mLocales == null || record.mLocales.equals(writeRecord.mLocales)) {
+ return false;
+ }
+ writeRecord.mLocales = record.mLocales;
+ return true;
+ }
+
@GuardedBy("mLock")
void removeUser(int userId) {
synchronized (mLock) {
@@ -210,7 +242,7 @@ public class PackageConfigPersister {
@GuardedBy("mLock")
void onPackageUninstall(String packageName) {
synchronized (mLock) {
- for (int i = mModified.size() - 1; i > 0; i--) {
+ for (int i = mModified.size() - 1; i >= 0; i--) {
final int userId = mModified.keyAt(i);
removePackage(packageName, userId);
}
@@ -238,11 +270,30 @@ public class PackageConfigPersister {
}
}
+ /**
+ * Retrieves and returns application configuration from persisted records if it exists, else
+ * returns null.
+ */
+ ActivityTaskManagerInternal.PackageConfig findPackageConfiguration(String packageName,
+ int userId) {
+ synchronized (mLock) {
+ PackageConfigRecord packageConfigRecord = findRecord(mModified, packageName, userId);
+ if (packageConfigRecord == null) {
+ Slog.w(TAG, "App-specific configuration not found for packageName: " + packageName
+ + " and userId: " + userId);
+ return null;
+ }
+ return new ActivityTaskManagerInternal.PackageConfig(
+ packageConfigRecord.mNightMode, packageConfigRecord.mLocales);
+ }
+ }
+
// store a changed data so we don't need to get the process
static class PackageConfigRecord {
final String mName;
final int mUserId;
- int mNightMode;
+ Integer mNightMode;
+ LocaleList mLocales;
PackageConfigRecord(String name, int userId) {
mName = name;
@@ -250,13 +301,13 @@ public class PackageConfigPersister {
}
boolean isResetNightMode() {
- return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM;
+ return mNightMode == Configuration.UI_MODE_NIGHT_UNDEFINED;
}
@Override
public String toString() {
return "PackageConfigRecord package name: " + mName + " userId " + mUserId
- + " nightMode " + mNightMode;
+ + " nightMode " + mNightMode + " locales " + mLocales;
}
}
@@ -369,7 +420,13 @@ public class PackageConfigPersister {
}
xmlSerializer.startTag(null, TAG_CONFIG);
xmlSerializer.attribute(null, ATTR_PACKAGE_NAME, mRecord.mName);
- xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+ if (mRecord.mNightMode != null) {
+ xmlSerializer.attributeInt(null, ATTR_NIGHT_MODE, mRecord.mNightMode);
+ }
+ if (mRecord.mLocales != null) {
+ xmlSerializer.attribute(null, ATTR_LOCALES, mRecord.mLocales
+ .toLanguageTags());
+ }
xmlSerializer.endTag(null, TAG_CONFIG);
xmlSerializer.endDocument();
xmlSerializer.flush();
diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
new file mode 100644
index 000000000000..8bbcf1f9c029
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
@@ -0,0 +1,126 @@
+/*
+ * 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.wm;
+
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.LocaleList;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import java.util.Optional;
+
+/**
+ * An implementation of {@link ActivityTaskManagerInternal.PackageConfigurationUpdater}.
+ */
+final class PackageConfigurationUpdaterImpl implements
+ ActivityTaskManagerInternal.PackageConfigurationUpdater {
+ private static final String TAG = "PackageConfigurationUpdaterImpl";
+ private final Optional<Integer> mPid;
+ private Integer mNightMode;
+ private LocaleList mLocales;
+ private String mPackageName;
+ private int mUserId;
+ private ActivityTaskManagerService mAtm;
+
+ PackageConfigurationUpdaterImpl(int pid, ActivityTaskManagerService atm) {
+ mPid = Optional.of(pid);
+ mAtm = atm;
+ }
+
+ PackageConfigurationUpdaterImpl(String packageName, int userId,
+ ActivityTaskManagerService atm) {
+ mPackageName = packageName;
+ mUserId = userId;
+ mAtm = atm;
+ mPid = Optional.empty();
+ }
+
+ @Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater setNightMode(int nightMode) {
+ synchronized (this) {
+ mNightMode = nightMode;
+ }
+ return this;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal.PackageConfigurationUpdater
+ setLocales(LocaleList locales) {
+ synchronized (this) {
+ mLocales = locales;
+ }
+ return this;
+ }
+
+ @Override
+ public void commit() {
+ synchronized (this) {
+ synchronized (mAtm.mGlobalLock) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final int uid;
+ if (mPid.isPresent()) {
+ WindowProcessController wpc = mAtm.mProcessMap.getProcess(mPid.get());
+ if (wpc == null) {
+ Slog.w(TAG, "commit: Override application configuration failed: "
+ + "cannot find pid " + mPid);
+ return;
+ }
+ uid = wpc.mUid;
+ mUserId = wpc.mUserId;
+ mPackageName = wpc.mInfo.packageName;
+ } else {
+ uid = mAtm.getPackageManagerInternalLocked().getPackageUid(mPackageName,
+ /* flags = */ PackageManager.MATCH_ALL, mUserId);
+ if (uid < 0) {
+ Slog.w(TAG, "commit: update of application configuration failed: "
+ + "userId or packageName not valid " + mUserId);
+ return;
+ }
+ }
+ updateConfig(uid, mPackageName);
+ mAtm.mPackageConfigPersister.updateFromImpl(mPackageName, mUserId, this);
+
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+
+ private void updateConfig(int uid, String packageName) {
+ final ArraySet<WindowProcessController> processes = mAtm.mProcessMap.getProcesses(uid);
+ if (processes == null) return;
+ for (int i = processes.size() - 1; i >= 0; i--) {
+ final WindowProcessController wpc = processes.valueAt(i);
+ if (!wpc.mInfo.packageName.equals(packageName)) continue;
+ LocaleList localesOverride = LocaleOverlayHelper.combineLocalesIfOverlayExists(
+ mLocales, mAtm.getGlobalConfiguration().getLocales());
+ wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+ wpc.updateAppSpecificSettingsForAllActivities(mNightMode, localesOverride);
+ }
+ }
+
+ Integer getNightMode() {
+ return mNightMode;
+ }
+
+ LocaleList getLocales() {
+ return mLocales;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 7e95e7d2aa8c..1da0fe731709 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -173,10 +172,8 @@ class PinnedTaskController {
* to avoid flickering when running PiP animation across different orientations.
*/
void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() {
- final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea()
- .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
- final ActivityRecord topFullscreen = topFullscreenTask != null
- ? topFullscreenTask.topRunningActivity() : null;
+ final ActivityRecord topFullscreen = mDisplayContent.getActivity(
+ a -> a.fillsParent() && !a.getTask().inMultiWindowMode());
if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) {
return;
}
@@ -211,7 +208,9 @@ class PinnedTaskController {
}
mFreezingTaskConfig = true;
mDestRotatedBounds = new Rect(bounds);
- continueOrientationChange();
+ if (!mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ continueOrientationChange();
+ }
}
/**
@@ -244,7 +243,8 @@ class PinnedTaskController {
int oldRotation, int newRotation) {
final Rect bounds = mDestRotatedBounds;
final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
- if (bounds == null && pipTx == null) {
+ final boolean emptyPipPositionTx = pipTx == null || pipTx.mPosition == null;
+ if (bounds == null && emptyPipPositionTx) {
return;
}
final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea();
@@ -256,25 +256,27 @@ class PinnedTaskController {
mDestRotatedBounds = null;
mPipTransaction = null;
final Rect areaBounds = taskArea.getBounds();
- if (pipTx != null) {
+ if (!emptyPipPositionTx) {
// The transaction from recents animation is in old rotation. So the position needs to
// be rotated.
- float dx = pipTx.mPositionX;
- float dy = pipTx.mPositionY;
+ float dx = pipTx.mPosition.x;
+ float dy = pipTx.mPosition.y;
final Matrix matrix = pipTx.getMatrix();
if (pipTx.mRotation == 90) {
- dx = pipTx.mPositionY;
- dy = areaBounds.right - pipTx.mPositionX;
+ dx = pipTx.mPosition.y;
+ dy = areaBounds.right - pipTx.mPosition.x;
matrix.postRotate(-90);
} else if (pipTx.mRotation == -90) {
- dx = areaBounds.bottom - pipTx.mPositionY;
- dy = pipTx.mPositionX;
+ dx = areaBounds.bottom - pipTx.mPosition.y;
+ dy = pipTx.mPosition.x;
matrix.postRotate(90);
}
matrix.postTranslate(dx, dy);
final SurfaceControl leash = pinnedTask.getSurfaceControl();
- t.setMatrix(leash, matrix, new float[9])
- .setCornerRadius(leash, pipTx.mCornerRadius);
+ t.setMatrix(leash, matrix, new float[9]);
+ if (pipTx.hasCornerRadiusSet()) {
+ t.setCornerRadius(leash, pipTx.mCornerRadius);
+ }
Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
return;
}
@@ -317,15 +319,11 @@ class PinnedTaskController {
}
/** Resets the states which were used to perform fixed rotation with PiP task. */
- void onCancelFixedRotationTransform(Task task) {
+ void onCancelFixedRotationTransform() {
mFreezingTaskConfig = false;
mDeferOrientationChanging = false;
mDestRotatedBounds = null;
mPipTransaction = null;
- if (!task.isOrganized()) {
- // Force clearing Task#mForceNotOrganized because the display didn't rotate.
- task.onConfigurationChanged(task.getParent().getConfiguration());
- }
}
/**
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 11b63b4417ab..11d1270b48c3 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1110,13 +1110,15 @@ class RecentTasks {
}
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
- removeForAddTask(task);
+ final int removedIndex = removeForAddTask(task);
task.inRecents = true;
if (!isAffiliated || needAffiliationFix) {
// If this is a simple non-affiliated task, or we had some failure trying to
// handle it as part of an affilated task, then just place it at the top.
- mTasks.add(0, task);
+ // But if the list is frozen, adding the task to the removed index to keep the order.
+ int indexToAdd = mFreezeTaskListReordering && removedIndex != -1 ? removedIndex : 0;
+ mTasks.add(indexToAdd, task);
notifyTaskAdded(task);
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
} else if (isAffiliated) {
@@ -1357,7 +1359,8 @@ class RecentTasks {
+ " activityType=" + task.getActivityType()
+ " windowingMode=" + task.getWindowingMode()
+ " isAlwaysOnTopWhenVisible=" + task.isAlwaysOnTopWhenVisible()
- + " intentFlags=" + task.getBaseIntent().getFlags());
+ + " intentFlags=" + task.getBaseIntent().getFlags()
+ + " isEmbedded=" + task.isEmbedded());
}
switch (task.getActivityType()) {
@@ -1403,6 +1406,11 @@ class RecentTasks {
return false;
}
+ // Ignore the task if it is a embedded task
+ if (task.isEmbedded()) {
+ return false;
+ }
+
return true;
}
@@ -1495,14 +1503,14 @@ class RecentTasks {
* If needed, remove oldest existing entries in recents that are for the same kind
* of task as the given one.
*/
- private void removeForAddTask(Task task) {
+ private int removeForAddTask(Task task) {
// The adding task will be in recents so it is not hidden.
mHiddenTasks.remove(task);
final int removeIndex = findRemoveIndexForAddTask(task);
if (removeIndex == -1) {
// Nothing to trim
- return;
+ return removeIndex;
}
// There is a similar task that will be removed for the addition of {@param task}, but it
@@ -1524,6 +1532,7 @@ class RecentTasks {
}
}
notifyTaskPersisterLocked(removedTask, false /* flush */);
+ return removeIndex;
}
/**
@@ -1531,11 +1540,6 @@ class RecentTasks {
* list (if any).
*/
private int findRemoveIndexForAddTask(Task task) {
- if (mFreezeTaskListReordering) {
- // Defer removing tasks due to the addition of new tasks until the task list is unfrozen
- return -1;
- }
-
final int recentsCount = mTasks.size();
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index fea52f2f3b8d..6d96cf06a00b 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -25,6 +25,8 @@ import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
@@ -123,6 +125,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "Updated config=%s",
targetActivity.getConfiguration());
}
+ } else if (mDefaultTaskDisplayArea.getActivity(
+ ActivityRecord::occludesParent, false /* traverseTopToBottom */) == null) {
+ // Skip because none of above activities can occlude the target activity. The preload
+ // should be done silently in background without being visible.
+ return;
} else {
// Create the activity record. Because the activity is invisible, this doesn't really
// start the client.
@@ -149,8 +156,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
// Invisible activity should be stopped. If the recents activity is alive and its doesn't
// need to relaunch by current configuration, then it may be already in stopped state.
- if (!targetActivity.isState(Task.ActivityState.STOPPING,
- Task.ActivityState.STOPPED)) {
+ if (!targetActivity.isState(STOPPING, STOPPED)) {
// Add to stopping instead of stop immediately. So the client has the chance to perform
// traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more
// things (e.g. the measure can be done earlier). The actual stop will be performed when
@@ -467,7 +473,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
*/
static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) {
try {
- recentsAnimationRunner.onAnimationCanceled(null /* taskSnapshot */);
+ recentsAnimationRunner.onAnimationCanceled(null /* taskIds */,
+ null /* taskSnapshots */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to cancel recents animation before start", e);
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index e346e3ec7db9..5dd8ef39e8e7 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -123,6 +124,7 @@ public class RecentsAnimationController implements DeathRecipient {
private final int mDisplayId;
private boolean mWillFinishToHome = false;
private final Runnable mFailsafeRunnable = this::onFailsafe;
+ private Runnable mCheckRotationAfterCleanup;
// The recents component app token that is shown behind the visibile tasks
private ActivityRecord mTargetActivityRecord;
@@ -162,14 +164,17 @@ public class RecentsAnimationController implements DeathRecipient {
private boolean mNavigationBarAttachedToApp;
private ActivityRecord mNavBarAttachedApp;
+ private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>();
+
/**
* An app transition listener to cancel the recents animation only after the app transition
* starts or is canceled.
*/
final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
continueDeferredCancel();
return 0;
}
@@ -259,13 +264,6 @@ public class RecentsAnimationController implements DeathRecipient {
"finish(%b): mCanceled=%b", moveHomeToTop, mCanceled);
final long token = Binder.clearCallingIdentity();
try {
- synchronized (mService.getWindowManagerLock()) {
- // Remove all new task targets.
- for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
- removeTaskInternal(mPendingNewTaskTargets.get(i));
- }
- }
-
// Note, the callback will handle its own synchronization, do not lock on WM lock
// prior to calling the callback
mCallbacks.onAnimationFinished(moveHomeToTop
@@ -581,7 +579,7 @@ public class RecentsAnimationController implements DeathRecipient {
contentInsets = targetAppMainWindow
.getInsetsStateWithVisibilityOverride()
.calculateInsets(mTargetActivityRecord.getBounds(), Type.systemBars(),
- false /* ignoreVisibility */);
+ false /* ignoreVisibility */).toRect();
} else {
// If the window for the activity had not yet been created, use the display insets.
mService.getStableInsets(mDisplayId, mTmpRect);
@@ -730,11 +728,19 @@ public class RecentsAnimationController implements DeathRecipient {
return;
}
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target);
- try {
- mRunner.onTaskAppeared(target);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to report task appeared", e);
- }
+ mPendingTaskAppears.add(target);
+ }
+ }
+
+ void sendTasksAppeared() {
+ if (mPendingTaskAppears.isEmpty() || mRunner == null) return;
+ try {
+ final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray(
+ new RemoteAnimationTarget[0]);
+ mRunner.onTasksAppeared(targets);
+ mPendingTaskAppears.clear();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report task appeared", e);
}
}
@@ -742,10 +748,15 @@ public class RecentsAnimationController implements DeathRecipient {
OnAnimationFinishedCallback finishedCallback) {
final SparseBooleanArray recentTaskIds =
mService.mAtmService.getRecentTasks().getRecentTaskIds();
- TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
- !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback);
- mPendingNewTaskTargets.add(task.mTaskId);
- return adapter.createRemoteAnimationTarget();
+ // The target must be built off the root task (the leaf task surface would be cropped
+ // within the root surface). However, recents only tracks leaf task ids, so we'll replace
+ // the task-id with the leaf id.
+ final Task leafTask = task.getTopLeafTask();
+ int taskId = leafTask.mTaskId;
+ TaskAnimationAdapter adapter = addAnimation(task,
+ !recentTaskIds.get(taskId), true /* hidden */, finishedCallback);
+ mPendingNewTaskTargets.add(taskId);
+ return adapter.createRemoteAnimationTarget(taskId);
}
void logRecentsAnimationStartTime(int durationMs) {
@@ -780,7 +791,8 @@ public class RecentsAnimationController implements DeathRecipient {
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
- final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget();
+ final RemoteAnimationTarget target =
+ taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID);
if (target != null) {
targets.add(target);
} else {
@@ -792,7 +804,7 @@ public class RecentsAnimationController implements DeathRecipient {
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent, 0L, 0L,
adapter -> {
synchronized (mService.mGlobalLock) {
// If the wallpaper animation is canceled, continue with the recents
@@ -851,37 +863,37 @@ public class RecentsAnimationController implements DeathRecipient {
mCanceled = true;
if (screenshot && !mPendingAnimations.isEmpty()) {
- final TaskAnimationAdapter adapter = mPendingAnimations.get(0);
- final Task task = adapter.mTask;
- // Screen shot previous task when next task starts transition and notify the runner.
- // We will actually finish the animation once the runner calls cleanUpScreenshot().
- final TaskSnapshot taskSnapshot = screenshotRecentTask(task);
+ final ArrayMap<Task, TaskSnapshot> snapshotMap = screenshotRecentTasks();
mPendingCancelWithScreenshotReorderMode = reorderMode;
- try {
- mRunner.onAnimationCanceled(taskSnapshot);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to cancel recents animation", e);
- }
- if (taskSnapshot != null) {
- // Defer until the runner calls back to cleanupScreenshot()
- adapter.setSnapshotOverlay(taskSnapshot);
+
+ if (!snapshotMap.isEmpty()) {
+ try {
+ int[] taskIds = new int[snapshotMap.size()];
+ TaskSnapshot[] snapshots = new TaskSnapshot[snapshotMap.size()];
+ for (int i = snapshotMap.size() - 1; i >= 0; i--) {
+ taskIds[i] = snapshotMap.keyAt(i).mTaskId;
+ snapshots[i] = snapshotMap.valueAt(i);
+ }
+ mRunner.onAnimationCanceled(taskIds, snapshots);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to cancel recents animation", e);
+ }
// Schedule a new failsafe for if the runner doesn't clean up the screenshot
scheduleFailsafe();
- } else {
- // Do a normal cancel since we couldn't screenshot
- mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
+ return;
}
- } else {
- // Otherwise, notify the runner and clean up the animation immediately
- // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls
- // to the runner if we this actually triggers cancel twice on the caller
- try {
- mRunner.onAnimationCanceled(null /* taskSnapshot */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to cancel recents animation", e);
- }
- mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
+ // Fallback to a normal cancel since we couldn't screenshot
}
+
+ // Notify the runner and clean up the animation immediately
+ // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls
+ // to the runner if we this actually triggers cancel twice on the caller
+ try {
+ mRunner.onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to cancel recents animation", e);
+ }
+ mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */);
}
}
@@ -921,6 +933,24 @@ public class RecentsAnimationController implements DeathRecipient {
}
/**
+ * If the display rotation change is ignored while recents animation is running, make sure that
+ * the pending rotation change will be applied after the animation finishes.
+ */
+ void setCheckRotationAfterCleanup() {
+ if (mCheckRotationAfterCleanup != null) return;
+ mCheckRotationAfterCleanup = () -> {
+ synchronized (mService.mGlobalLock) {
+ if (mDisplayContent.getDisplayRotation()
+ .updateRotationAndSendNewConfigIfChanged()) {
+ if (mTargetActivityRecord != null) {
+ mTargetActivityRecord.finishFixedRotationTransform();
+ }
+ }
+ }
+ };
+ }
+
+ /**
* @return Whether we should defer the cancel from a root task order change until the next app
* transition.
*/
@@ -937,13 +967,23 @@ public class RecentsAnimationController implements DeathRecipient {
return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot;
}
- TaskSnapshot screenshotRecentTask(Task task) {
+ private ArrayMap<Task, TaskSnapshot> screenshotRecentTasks() {
final TaskSnapshotController snapshotController = mService.mTaskSnapshotController;
- final ArraySet<Task> tasks = Sets.newArraySet(task);
- snapshotController.snapshotTasks(tasks);
- snapshotController.addSkipClosingAppSnapshotTasks(tasks);
- return snapshotController.getSnapshot(task.mTaskId, task.mUserId,
- false /* restoreFromDisk */, false /* isLowResolution */);
+ final ArrayMap<Task, TaskSnapshot> snapshotMap = new ArrayMap<>();
+ for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+ final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
+ final Task task = adapter.mTask;
+ snapshotController.recordTaskSnapshot(task, false /* allowSnapshotHome */);
+ final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId,
+ false /* restoreFromDisk */, false /* isLowResolution */);
+ if (snapshot != null) {
+ snapshotMap.put(task, snapshot);
+ // Defer until the runner calls back to cleanupScreenshot()
+ adapter.setSnapshotOverlay(snapshot);
+ }
+ }
+ snapshotController.addSkipClosingAppSnapshotTasks(snapshotMap.keySet());
+ return snapshotMap;
}
void cleanupAnimation(@ReorderMode int reorderMode) {
@@ -965,6 +1005,9 @@ public class RecentsAnimationController implements DeathRecipient {
removeAnimation(taskAdapter);
taskAdapter.onCleanup();
}
+ // Should already be empty, but clean-up pending task-appears in-case they weren't sent.
+ mPendingNewTaskTargets.clear();
+ mPendingTaskAppears.clear();
for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
@@ -1007,6 +1050,10 @@ public class RecentsAnimationController implements DeathRecipient {
if (mStatusBar != null) {
mStatusBar.onRecentsAnimationStateChanged(false /* running */);
}
+ if (mCheckRotationAfterCleanup != null) {
+ mService.mH.post(mCheckRotationAfterCleanup);
+ mCheckRotationAfterCleanup = null;
+ }
}
void scheduleFailsafe() {
@@ -1102,6 +1149,13 @@ public class RecentsAnimationController implements DeathRecipient {
return mTargetActivityRecord.findMainWindow();
}
+ DisplayArea getTargetAppDisplayArea() {
+ if (mTargetActivityRecord == null) {
+ return null;
+ }
+ return mTargetActivityRecord.getDisplayArea();
+ }
+
boolean isAnimatingTask(Task task) {
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
if (task == mPendingAnimations.get(i).mTask) {
@@ -1183,7 +1237,14 @@ public class RecentsAnimationController implements DeathRecipient {
mLocalBounds.offsetTo(tmpPos.x, tmpPos.y);
}
- RemoteAnimationTarget createRemoteAnimationTarget() {
+ /**
+ * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus
+ * can differ from taskInfo. This mismatch is needed, however, in
+ * some cases where we are animating root tasks but need need leaf
+ * ids for identification. If this is INVALID (-1), then mTaskId
+ * will be used.
+ */
+ RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId) {
final ActivityRecord topApp = mTask.getTopVisibleActivity();
final WindowState mainWindow = topApp != null
? topApp.findMainWindow()
@@ -1192,16 +1253,20 @@ public class RecentsAnimationController implements DeathRecipient {
return null;
}
final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
- mBounds, Type.systemBars(), false /* ignoreVisibility */);
+ mBounds, Type.systemBars(), false /* ignoreVisibility */).toRect();
InsetUtils.addInsets(insets, mainWindow.mActivityRecord.getLetterboxInsets());
final int mode = topApp.getActivityType() == mTargetActivityType
? MODE_OPENING
: MODE_CLOSING;
- mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash,
+ if (overrideTaskId < 0) {
+ overrideTaskId = mTask.mTaskId;
+ }
+ mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash,
!topApp.fillsParent(), new Rect(),
insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top),
mLocalBounds, mBounds, mTask.getWindowConfiguration(),
- mIsRecentTaskInvisible, null, null, mTask.getTaskInfo());
+ mIsRecentTaskInvisible, null, null, mTask.getTaskInfo(),
+ topApp.checkEnterPictureInPictureAppOpsState());
return mTarget;
}
@@ -1289,7 +1354,7 @@ public class RecentsAnimationController implements DeathRecipient {
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
// Restore position and root task crop until client has a chance to modify it.
t.setPosition(animationLeash, mLocalBounds.left, mLocalBounds.top);
mTmpRect.set(mLocalBounds);
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 1a429f88fe2e..eeac230489f9 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -22,6 +22,7 @@ import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
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.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
@@ -65,7 +66,6 @@ class RemoteAnimationController implements DeathRecipient {
new ArrayList<>();
@VisibleForTesting
final ArrayList<NonAppWindowAnimationAdapter> mPendingNonAppAnimations = new ArrayList<>();
- private final Rect mTmpRect = new Rect();
private final Handler mHandler;
private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -85,18 +85,18 @@ class RemoteAnimationController implements DeathRecipient {
* Creates an animation record for each individual {@link WindowContainer}.
*
* @param windowContainer The windows to animate.
- * @param position The position app bounds, in screen coordinates.
+ * @param position The position app bounds relative to its parent.
* @param localBounds The bounds of the app relative to its parent.
- * @param stackBounds The stack bounds of the app relative to position.
- * @param startBounds The stack bounds before the transition, in screen coordinates
+ * @param endBounds The end bounds after the transition, in screen coordinates.
+ * @param startBounds The start bounds before the transition, in screen coordinates.
* @return The record representing animation(s) to run on the app.
*/
RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
- Point position, Rect localBounds, Rect stackBounds, Rect startBounds) {
+ Point position, Rect localBounds, Rect endBounds, Rect startBounds) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
windowContainer);
final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
- localBounds, stackBounds, startBounds);
+ localBounds, endBounds, startBounds);
mPendingAnimations.add(adapters);
return adapters;
}
@@ -208,7 +208,7 @@ class RemoteAnimationController implements DeathRecipient {
if (wrappers.mThumbnailAdapter != null
&& wrappers.mThumbnailAdapter.mCapturedFinishCallback != null) {
wrappers.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(wrappers.mAdapter.mAnimationType,
+ .onAnimationFinished(wrappers.mThumbnailAdapter.mAnimationType,
wrappers.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
@@ -219,7 +219,7 @@ class RemoteAnimationController implements DeathRecipient {
private RemoteAnimationTarget[] createWallpaperAnimations() {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createWallpaperAnimations()");
- return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+ return WallpaperAnimationAdapter.startWallpaperAnimations(mDisplayContent,
mRemoteAnimationAdapter.getDuration(),
mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
adapter -> {
@@ -261,7 +261,7 @@ class RemoteAnimationController implements DeathRecipient {
}
if (adapters.mThumbnailAdapter != null) {
adapters.mThumbnailAdapter.mCapturedFinishCallback
- .onAnimationFinished(adapters.mAdapter.mAnimationType,
+ .onAnimationFinished(adapters.mThumbnailAdapter.mAnimationType,
adapters.mThumbnailAdapter);
}
mPendingAnimations.remove(i);
@@ -396,6 +396,7 @@ class RemoteAnimationController implements DeathRecipient {
RemoteAnimationTarget mTarget;
final WindowContainer mWindowContainer;
final Rect mStartBounds;
+ private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING;
RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
Rect endBounds, Rect startBounds) {
@@ -404,16 +405,17 @@ class RemoteAnimationController implements DeathRecipient {
mStartBounds = new Rect(startBounds);
mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
mStartBounds);
- mTmpRect.set(startBounds);
- mTmpRect.offsetTo(0, 0);
if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
- mThumbnailAdapter =
- new RemoteAnimationAdapterWrapper(this, new Point(0, 0), localBounds,
- mTmpRect, new Rect());
+ final Rect thumbnailLocalBounds = new Rect(startBounds);
+ thumbnailLocalBounds.offsetTo(0, 0);
+ // Snapshot is located at (0,0) of the animation leash. It doesn't have size
+ // change, so the startBounds is its end bounds, and no start bounds for it.
+ mThumbnailAdapter = new RemoteAnimationAdapterWrapper(this, new Point(0, 0),
+ thumbnailLocalBounds, startBounds, new Rect());
}
} else {
mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
- new Rect(endPos.x, endPos.y, endBounds.right, endBounds.bottom));
+ new Rect());
mStartBounds = null;
}
}
@@ -428,18 +430,25 @@ class RemoteAnimationController implements DeathRecipient {
return mTarget;
}
+ void setMode(@RemoteAnimationTarget.Mode int mode) {
+ mMode = mode;
+ }
+
int getMode() {
- final DisplayContent dc = mWindowContainer.getDisplayContent();
- final ActivityRecord topActivity = mWindowContainer.getTopMostActivity();
- // Note that opening/closing transitions are per-activity while changing transitions
- // are per-task.
- if (dc.mOpeningApps.contains(topActivity)) {
- return RemoteAnimationTarget.MODE_OPENING;
- } else if (dc.mChangingContainers.contains(mWindowContainer)) {
- return RemoteAnimationTarget.MODE_CHANGING;
- } else {
- return RemoteAnimationTarget.MODE_CLOSING;
+ return mMode;
+ }
+
+ /** Whether its parent is also an animation target in the same transition. */
+ boolean hasAnimatingParent() {
+ // mOpeningApps and mClosingApps are only activities, so only need to check
+ // mChangingContainers.
+ for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+ if (mWindowContainer.isDescendantOf(
+ mDisplayContent.mChangingContainers.valueAt(i))) {
+ return true;
+ }
}
+ return false;
}
}
@@ -450,15 +459,15 @@ class RemoteAnimationController implements DeathRecipient {
private @AnimationType int mAnimationType;
final Point mPosition = new Point();
final Rect mLocalBounds;
- final Rect mRootTaskBounds = new Rect();
+ final Rect mEndBounds = new Rect();
final Rect mStartBounds = new Rect();
RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
- Rect localBounds, Rect rootTaskBounds, Rect startBounds) {
+ Rect localBounds, Rect endBounds, Rect startBounds) {
mRecord = record;
mPosition.set(position.x, position.y);
mLocalBounds = localBounds;
- mRootTaskBounds.set(rootTaskBounds);
+ mEndBounds.set(endBounds);
mStartBounds.set(startBounds);
}
@@ -469,15 +478,20 @@ class RemoteAnimationController implements DeathRecipient {
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
- @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
- // Restore position and stack crop until client has a chance to modify it.
if (mStartBounds.isEmpty()) {
- t.setPosition(animationLeash, 0, 0);
- t.setWindowCrop(animationLeash, -1, -1);
+ // Restore position and stack crop until client has a chance to modify it.
+ t.setPosition(animationLeash, mPosition.x, mPosition.y);
+ t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height());
} else {
- t.setPosition(animationLeash, mStartBounds.left, mStartBounds.top);
+ // Offset the change animation leash to the relative start position in parent.
+ // (mPosition) is the relative end position in parent container.
+ // (mStartBounds - mEndBounds) is the position difference between start and end.
+ // (mPosition + mStartBounds - mEndBounds) will be the relative start position.
+ t.setPosition(animationLeash, mPosition.x + mStartBounds.left - mEndBounds.left,
+ mPosition.y + mStartBounds.top - mEndBounds.top);
t.setWindowCrop(animationLeash, mStartBounds.width(), mStartBounds.height());
}
mCapturedLeash = animationLeash;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6844656b6e09..99f9978ce9fb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -37,10 +37,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -48,11 +48,18 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_O
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.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
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;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
@@ -67,20 +74,14 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.KeyguardController.KEYGUARD_SLEEP_TOKEN_TAG;
+import static com.android.server.wm.RootWindowContainerProto.DEFAULT_MIN_SIZE_RESIZABLE_TASK;
import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -373,8 +374,26 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
return false;
}
+ if (matchingCandidate(task)) {
+ return true;
+ }
+
+ // Looking for the embedded tasks (if any)
+ return !task.isLeafTaskFragment() && task.forAllLeafTaskFragments(
+ this::matchingCandidate);
+ }
+
+ boolean matchingCandidate(TaskFragment taskFragment) {
+ final Task task = taskFragment.asTask();
+ if (task == null) {
+ return false;
+ }
+
// Overlays should not be considered as the task's logical top activity.
- final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */);
+ // Activities of the tasks that embedded from this one should not be used.
+ final ActivityRecord r = task.getTopNonFinishingActivity(false /* includeOverlays */,
+ false /* includingEmbeddedTask */);
+
if (r == null || r.finishing || r.mUserId != userId
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
ProtoLog.d(WM_DEBUG_TASKS, "Skipping %s: mismatch root %s", task, r);
@@ -494,6 +513,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
mTopFocusedDisplayId = topFocusedDisplayId;
mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
+ mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);
ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
}
return changed;
@@ -517,6 +537,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
mTaskSupervisor.updateTopResumedActivityIfNeeded();
}
+ @Override
+ boolean isAttached() {
+ return true;
+ }
+
/**
* Called when DisplayWindowSettings values may change.
*/
@@ -825,8 +850,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
// Initialize state of exiting tokens.
- final int numDisplays = mChildren.size();
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
displayContent.setExitingTokensHasVisible(false);
}
@@ -863,6 +887,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// Send any pending task-info changes that were queued-up during a layout deferment
mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
+ mWmService.mAtmService.mTaskFragmentOrganizerController.dispatchPendingEvents();
mWmService.mSyncEngine.onSurfacePlacement();
mWmService.mAnimator.executeAfterPrepareSurfacesRunnables();
@@ -875,10 +900,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
}
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.mWallpaperMayChange) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Wallpaper may change! Adjusting");
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
if (DEBUG_LAYOUT_REPEATS) {
surfacePlacer.debugLayoutRepeats("WallpaperMayChange",
@@ -937,12 +962,12 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
// Time to remove any exiting tokens?
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
displayContent.removeExistingTokensIfPossible();
}
- for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);
if (displayContent.pendingLayoutChanges != 0) {
displayContent.setLayoutNeeded();
@@ -1238,6 +1263,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
pw.println(mTopFocusedDisplayId);
}
+ void dumpDefaultMinSizeOfResizableTask(PrintWriter pw) {
+ pw.print(" mDefaultMinSizeOfResizeableTaskDp=");
+ pw.println(mDefaultMinSizeOfResizeableTaskDp);
+ }
+
void dumpLayoutNeededDisplayIds(PrintWriter pw) {
if (!isLayoutNeeded()) {
return;
@@ -1284,7 +1314,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
mTaskSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
proto.write(IS_HOME_RECENTS_COMPONENT,
mTaskSupervisor.mRecentTasks.isRecentsComponentHomeActivity(mCurrentUser));
-
+ proto.write(DEFAULT_MIN_SIZE_RESIZABLE_TASK, mDefaultMinSizeOfResizeableTaskDp);
proto.end(token);
}
@@ -1873,7 +1903,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
if (focusedRootTask == null) {
return null;
}
- final ActivityRecord resumedActivity = focusedRootTask.getResumedActivity();
+ final ActivityRecord resumedActivity = focusedRootTask.getTopResumedActivity();
if (resumedActivity != null && resumedActivity.app != null) {
return resumedActivity;
}
@@ -1895,11 +1925,11 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// foreground.
WindowProcessController fgApp = getItemFromRootTasks(rootTask -> {
if (isTopDisplayFocusedRootTask(rootTask)) {
- final ActivityRecord resumedActivity = rootTask.getResumedActivity();
+ final ActivityRecord resumedActivity = rootTask.getTopResumedActivity();
if (resumedActivity != null) {
return resumedActivity.app;
- } else if (rootTask.getPausingActivity() != null) {
- return rootTask.getPausingActivity().app;
+ } else if (rootTask.getTopPausingActivity() != null) {
+ return rootTask.getTopPausingActivity().app;
}
}
return null;
@@ -1925,7 +1955,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
return;
}
- if (rootTask.getVisibility(null /*starting*/) == TASK_VISIBILITY_INVISIBLE) {
+ if (rootTask.getVisibility(null /* starting */)
+ == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
return;
}
@@ -1949,7 +1980,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r,
WindowProcessController app, ActivityRecord top) {
- if (r.finishing || !r.okToShowLocked() || !r.visibleIgnoringKeyguard || r.app != null
+ if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard || r.app != null
|| app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
return false;
}
@@ -1982,7 +2013,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
*/
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
- if (mTaskSupervisor.inActivityVisibilityUpdate()) {
+ if (mTaskSupervisor.inActivityVisibilityUpdate()
+ || mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
// Don't do recursive work.
return;
}
@@ -2197,7 +2229,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
// display area, so reparent.
rootTask.reparent(taskDisplayArea, true /* onTop */);
}
- mService.getTransitionController().requestTransitionIfNeeded(TRANSIT_CHANGE, rootTask);
+ rootTask.mTransitionController.requestTransitionIfNeeded(TRANSIT_PIP, rootTask);
// Defer the windowing mode change until after the transition to prevent the activity
// from doing work and changing the activity visuals while animating
@@ -2502,7 +2534,9 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
if (displayShouldSleep) {
rootTask.goToSleepIfPossible(false /* shuttingDown */);
} else {
- rootTask.awakeFromSleepingLocked();
+ rootTask.forAllLeafTasksAndLeafTaskFragments(
+ taskFragment -> taskFragment.awakeFromSleeping(),
+ true /* traverseTopToBottom */);
if (rootTask.isFocusedRootTaskOnDisplay()
&& !mTaskSupervisor.getKeyguardController()
.isKeyguardOrAodShowing(display.mDisplayId)) {
@@ -2768,6 +2802,7 @@ public 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);
} else {
throw new RuntimeException("Create the same sleep token twice: " + token);
}
@@ -2786,6 +2821,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
return;
}
+ ProtoLog.d(WM_DEBUG_STATES, "Remove sleep token: tag=%s, displayId=%d", token.mTag,
+ token.mDisplayId);
display.mAllSleepTokens.remove(token);
if (display.mAllSleepTokens.isEmpty()) {
mService.updateSleepIfNeededLocked();
@@ -2879,8 +2916,8 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
if (DEBUG_SWITCH) {
Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.getState()
- + " resumed=" + r.getTask().getResumedActivity() + " pausing="
- + r.getTask().getPausingActivity() + " for reason "
+ + " resumed=" + r.getTask().getTopResumedActivity() + " pausing="
+ + r.getTask().getTopPausingActivity() + " for reason "
+ mDestroyAllActivitiesReason);
}
@@ -2914,7 +2951,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
r.detachFromProcess();
- r.mDisplayContent.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
r.mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE,
TRANSIT_FLAG_APP_CRASHED);
r.destroyIfPossible("handleAppCrashed");
@@ -3467,7 +3503,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
if (rootTask == null || !rootTask.hasActivity()) {
continue;
}
- final ActivityRecord resumedActivity = rootTask.getResumedActivity();
+ final ActivityRecord resumedActivity = rootTask.getTopResumedActivity();
if (resumedActivity == null || !resumedActivity.idle) {
ProtoLog.d(WM_DEBUG_STATES, "allResumedActivitiesIdle: rootTask=%d %s "
+ "not idle", rootTask.getRootTaskId(), resumedActivity);
@@ -3482,7 +3518,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
boolean allResumedActivitiesVisible() {
boolean[] foundResumed = {false};
final boolean foundInvisibleResumedActivity = forAllRootTasks(rootTask -> {
- final ActivityRecord r = rootTask.getResumedActivity();
+ final ActivityRecord r = rootTask.getTopResumedActivity();
if (r != null) {
if (!r.nowVisible) {
return true;
@@ -3500,7 +3536,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
boolean allPausedActivitiesComplete() {
boolean[] pausing = {true};
final boolean hasActivityNotCompleted = forAllLeafTasks(task -> {
- final ActivityRecord r = task.getPausingActivity();
+ final ActivityRecord r = task.getTopPausingActivity();
if (r != null && !r.isState(PAUSED, STOPPED, STOPPING, FINISHING)) {
ProtoLog.d(WM_DEBUG_STATES, "allPausedActivitiesComplete: "
+ "r=%s state=%s", r, r.getState());
@@ -3543,14 +3579,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}, true /* traverseTopToBottom */);
}
- void cancelInitializingActivities() {
- forAllRootTasks(task -> {
- // We don't want to clear starting window for activities that aren't occluded
- // as we need to display their starting window until they are done initializing.
- task.forAllOccludedActivities(ActivityRecord::cancelInitializing);
- });
- }
-
Task anyTaskForId(int id) {
return anyTaskForId(id, MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE);
}
@@ -3630,11 +3658,6 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
return task;
}
- ActivityRecord isInAnyTask(IBinder token) {
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return (r != null && r.isDescendantOf(this)) ? r : null;
- }
-
@VisibleForTesting
void getRunningTasks(int maxNum, List<ActivityManager.RunningTaskInfo> list,
int flags, int callingUid, ArraySet<Integer> profileIds) {
@@ -3643,14 +3666,7 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
}
void startPowerModeLaunchIfNeeded(boolean forceSend, ActivityRecord targetActivity) {
- final boolean sendPowerModeLaunch;
-
- if (forceSend) {
- sendPowerModeLaunch = true;
- } else if (targetActivity == null || targetActivity.app == null) {
- // Set power mode if we don't know what we're launching yet.
- sendPowerModeLaunch = true;
- } else {
+ if (!forceSend && targetActivity != null && targetActivity.app != null) {
// Set power mode when the activity's process is different than the current top resumed
// activity on all display areas, or if there are no resumed activities in the system.
boolean[] noResumedActivities = {true};
@@ -3666,15 +3682,32 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
!resumedActivityProcess.equals(targetActivity.app);
}
});
- sendPowerModeLaunch = noResumedActivities[0] || allFocusedProcessesDiffer[0];
+ if (!noResumedActivities[0] && !allFocusedProcessesDiffer[0]) {
+ // All focused activities are resumed and the process of the target activity is
+ // the same as them, e.g. delivering new intent to the current top.
+ return;
+ }
}
- if (sendPowerModeLaunch) {
- mService.startLaunchPowerMode(
- ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+ int reason = ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY;
+ // If the activity is launching while keyguard is locked (including occluded), the activity
+ // may be visible until its first relayout is done (e.g. apply show-when-lock flag). To
+ // avoid power mode from being cleared before that, add a special reason to consider whether
+ // the unknown visibility is resolved. The case from SystemUI is excluded because it should
+ // rely on keyguard-going-away.
+ if (mService.mKeyguardController.isKeyguardLocked() && targetActivity != null
+ && !targetActivity.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_SYSTEMUI)) {
+ final ActivityOptions opts = targetActivity.getOptions();
+ if (opts == null || opts.getSourceInfo() == null
+ || opts.getSourceInfo().type != ActivityOptions.SourceInfo.TYPE_LOCKSCREEN) {
+ reason |= ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY;
+ }
}
+ mService.startLaunchPowerMode(reason);
}
+ // TODO(b/191434136): handle this properly when we add multi-window support on secondary
+ // display.
private void calculateDefaultMinimalSizeOfResizeableTasks() {
final Resources res = mService.mContext.getResources();
final float minimalSize = res.getDimension(
@@ -3801,6 +3834,10 @@ public class RootWindowContainer extends WindowContainer<DisplayContent>
return "{\"" + mTag + "\", display " + mDisplayId
+ ", acquire at " + TimeUtils.formatUptime(mAcquireTime) + "}";
}
+
+ void writeTagToProto(ProtoOutputStream proto, long fieldId) {
+ proto.write(fieldId, mTag);
+ }
}
private class RankTaskLayersRunnable implements Runnable {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 7ba772c18455..9864297de529 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -54,6 +54,7 @@ class RunningTasks {
private boolean mAllowed;
private boolean mFilterOnlyVisibleRecents;
private Task mTopDisplayFocusRootTask;
+ private Task mTopDisplayAdjacentTask;
private RecentTasks mRecentTasks;
private boolean mKeepIntentExtra;
@@ -77,6 +78,12 @@ class RunningTasks {
mRecentTasks = root.mService.getRecentTasks();
mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
+ if (mTopDisplayFocusRootTask.getAdjacentTaskFragment() != null) {
+ mTopDisplayAdjacentTask = mTopDisplayFocusRootTask.getAdjacentTaskFragment().asTask();
+ } else {
+ mTopDisplayAdjacentTask = null;
+ }
+
final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this,
PooledLambda.__(Task.class));
root.forAllLeafTasks(c, false);
@@ -126,6 +133,12 @@ class RunningTasks {
// can be used to determine the order of the tasks (it may not be set for newly
// created tasks)
task.touchActiveTime();
+ } else if (rootTask == mTopDisplayAdjacentTask && rootTask.getTopMostTask() == task) {
+ // The short-term workaround for launcher could get suitable running task info in
+ // split screen.
+ task.touchActiveTime();
+ // TreeSet doesn't allow same value and make sure this task is lower than focus one.
+ task.lastActiveTime--;
}
mTmpSortedSet.add(task);
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 4892005631ba..2d4aef682d62 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -78,6 +78,18 @@ public class SafeActivityOptions {
}
/**
+ * Constructs a new instance from a bundle and provided pid/uid.
+ *
+ * @param bOptions The {@link ActivityOptions} as {@link Bundle}.
+ */
+ static SafeActivityOptions fromBundle(Bundle bOptions, int callingPid, int callingUid) {
+ return bOptions != null
+ ? new SafeActivityOptions(ActivityOptions.fromBundle(bOptions),
+ callingPid, callingUid)
+ : null;
+ }
+
+ /**
* Constructs a new instance and records {@link Binder#getCallingPid}/
* {@link Binder#getCallingUid}. Thus, calling identity MUST NOT be cleared when constructing
* this object.
@@ -91,6 +103,17 @@ public class SafeActivityOptions {
}
/**
+ * Constructs a new instance.
+ *
+ * @param options The options to wrap.
+ */
+ private SafeActivityOptions(@Nullable ActivityOptions options, int callingPid, int callingUid) {
+ mOriginalCallingPid = callingPid;
+ mOriginalCallingUid = callingUid;
+ mOriginalOptions = options;
+ }
+
+ /**
* 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.
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 1117191f8bf6..255905a5819d 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
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;
@@ -30,8 +31,6 @@ import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
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.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
import android.animation.ArgbEvaluator;
import android.content.Context;
@@ -92,13 +91,6 @@ import java.io.PrintWriter;
class ScreenRotationAnimation {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
- /*
- * Layers for screen rotation animation. We put these layers above
- * WINDOW_FREEZE_LAYER so that screen freeze will cover all windows.
- */
- private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
- private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE;
-
private BoostFramework mPerf = null;
private boolean mIsPerfLockAcquired = false;
@@ -429,7 +421,7 @@ class ScreenRotationAnimation {
finalWidth * 2, finalHeight * 2);
Rect inner = new Rect(0, 0, finalWidth, finalHeight);
mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
- SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false, mEnterBlackFrameLayer);
+ SCREEN_FREEZE_LAYER_BASE, mDisplayContent, false, mEnterBlackFrameLayer);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate black surface", e);
}
@@ -752,7 +744,12 @@ class ScreenRotationAnimation {
mScreenshotRotationAnimator = null;
mRotateScreenAnimator = null;
mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
- kill();
+ if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
+ // It also invokes kill().
+ mDisplayContent.setRotationAnimation(null);
+ } else {
+ kill();
+ }
mService.updateRotation(false, false);
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e28201245d9b..d8adc512b65a 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -69,6 +69,7 @@ import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
@@ -113,7 +114,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
private float mLastReportedAnimatorScale;
private String mPackageName;
private String mRelayoutTag;
- private final InsetsState mDummyRequestedVisibility = new InsetsState();
+ private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
public Session(WindowManagerService service, IWindowSessionCallback callback) {
@@ -187,29 +188,28 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsState requestedVisibility,
+ int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), requestedVisibility, outInputChannel, outInsetsState,
+ UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
outActiveControls);
}
-
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
+ int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
- requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
+ requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
}
@Override
public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsState outInsetsState) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), mDummyRequestedVisibility, null /* outInputChannel */,
+ UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
outInsetsState, mDummyControls);
}
@@ -299,6 +299,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
+
+ @Override
+ public boolean dropForAccessibility(IWindow window, int x, int y) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mDragDropController.dropForAccessibility(window, x, y);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Validates the given drag data.
*/
@@ -380,7 +391,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final ShortcutServiceInternal shortcutService =
LocalServices.getService(ShortcutServiceInternal.class);
final Intent[] shortcutIntents = shortcutService.createShortcutIntents(
- callingUid, callingPackage, packageName, shortcutId,
+ UserHandle.getUserId(callingUid), callingPackage, packageName, shortcutId,
user.getIdentifier(), callingPid, callingUid);
if (shortcutIntents == null || shortcutIntents.length == 0) {
throw new IllegalArgumentException("Invalid shortcut id");
@@ -624,12 +635,12 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public void insetsModified(IWindow window, InsetsState state) {
+ public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
synchronized (mService.mGlobalLock) {
final WindowState windowState = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (windowState != null) {
- windowState.updateRequestedVisibility(state);
+ windowState.setRequestedVisibilities(visibilities);
windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState);
}
}
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index be6a5d2fe27b..6ed59e96c700 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -197,7 +197,7 @@ public class ShellRoot {
mAccessibilityWindow = null;
}
}
- if (mDisplayContent.mWmService.mAccessibilityController != null) {
+ if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
mDisplayContent.getDisplayId());
}
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index c671e3835abc..8b1befbefd99 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -32,6 +32,12 @@ public abstract class StartingData {
*/
boolean mIsTransitionForward;
+ /**
+ * Non-null if the starting window should cover the bounds of associated task. It is assigned
+ * when the parent activity of starting window may be put in a partial area of the task.
+ */
+ Task mAssociatedTask;
+
protected StartingData(WindowManagerService service, int typeParams) {
mService = service;
mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index 48a7bdec2b94..cdf6b08b1c57 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -27,6 +27,7 @@ import android.graphics.Rect;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
+import android.view.WindowManagerPolicyConstants;
class StrictModeFlash {
private static final String TAG = TAG_WITH_CLASS_NAME ? "StrictModeFlash" : TAG_WM;
@@ -52,7 +53,7 @@ class StrictModeFlash {
.build();
// one more than Watermark? arbitrary.
- t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);
+ t.setLayer(ctrl, WindowManagerPolicyConstants.STRICT_MODE_LAYER);
t.setPosition(ctrl, 0, 0);
t.show(ctrl);
// Ensure we aren't considered as obscuring for Input purposes.
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index c7bf8ecfe949..50c9b31f425a 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -57,8 +57,11 @@ class SurfaceAnimator {
@VisibleForTesting
SurfaceControl mLeash;
@VisibleForTesting
+ SurfaceFreezer.Snapshot mSnapshot;
+ @VisibleForTesting
final Animatable mAnimatable;
- private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
+ @VisibleForTesting
+ final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
/**
* Static callback to run on all animations started through this SurfaceAnimator
@@ -151,12 +154,14 @@ class SurfaceAnimator {
* @param animationFinishedCallback The callback being triggered when the animation finishes.
* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
* cancel call to the underlying AnimationAdapter.
+ * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no
+ * snapshot.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
- @Nullable SurfaceFreezer freezer) {
+ @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
@@ -181,12 +186,20 @@ class SurfaceAnimator {
return;
}
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
+ if (snapshotAnim != null) {
+ mSnapshot = freezer.takeSnapshotForAnimation();
+ if (mSnapshot == null) {
+ Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable);
+ return;
+ }
+ mSnapshot.startAnimation(t, snapshotAnim, type);
+ }
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
- null /* animationCancelledCallback */, null /* freezer */);
+ null /* animationCancelledCallback */, null /* snapshotAnim */, null /* freezer */);
}
/**
@@ -328,6 +341,7 @@ class SurfaceAnimator {
final OnAnimationFinishedCallback animationFinishedCallback =
mSurfaceAnimationFinishedCallback;
final Runnable animationCancelledCallback = mAnimationCancelledCallback;
+ final SurfaceFreezer.Snapshot snapshot = mSnapshot;
reset(t, false);
if (animation != null) {
if (!mAnimationStartDelayed && forwardCancel) {
@@ -346,9 +360,14 @@ class SurfaceAnimator {
}
}
- if (forwardCancel && leash != null) {
- t.remove(leash);
- mService.scheduleAnimationLocked();
+ if (forwardCancel) {
+ if (snapshot != null) {
+ snapshot.cancelAnimation(t, false /* restarting */);
+ }
+ if (leash != null) {
+ t.remove(leash);
+ mService.scheduleAnimationLocked();
+ }
}
if (!restarting) {
@@ -361,6 +380,12 @@ class SurfaceAnimator {
mAnimation = null;
mSurfaceAnimationFinishedCallback = null;
mAnimationType = ANIMATION_TYPE_NONE;
+ final SurfaceFreezer.Snapshot snapshot = mSnapshot;
+ mSnapshot = null;
+ if (snapshot != null) {
+ // Reset the mSnapshot reference before calling the callback to prevent circular reset.
+ snapshot.cancelAnimation(t, !destroyLeash);
+ }
if (mLeash == null) {
return;
}
@@ -377,11 +402,15 @@ class SurfaceAnimator {
boolean scheduleAnim = false;
final SurfaceControl surface = animatable.getSurfaceControl();
final SurfaceControl parent = animatable.getParentSurfaceControl();
+ final SurfaceControl curAnimationLeash = animatable.getAnimationLeash();
// If the surface was destroyed or the leash is invalid, we don't care to reparent it back.
// Note that we also set this variable to true even if the parent isn't valid anymore, in
// order to ensure onAnimationLeashLost still gets called in this case.
- final boolean reparent = surface != null;
+ // If the animation leash is set, and it is different from the removing leash, it means the
+ // surface now has a new animation surface. We don't want to reparent for that.
+ final boolean reparent = surface != null && (curAnimationLeash == null
+ || curAnimationLeash.equals(leash));
if (reparent) {
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent);
// We shouldn't really need these isValid checks but we do
@@ -608,6 +637,14 @@ class SurfaceAnimator {
void onAnimationLeashLost(Transaction t);
/**
+ * Gets the last created animation leash that has not lost yet.
+ */
+ @Nullable
+ default SurfaceControl getAnimationLeash() {
+ return null;
+ }
+
+ /**
* @return A new surface to be used for the animation leash, inserted at the correct
* position in the hierarchy.
*/
diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java
index e0a791e118bb..a7ef36b01d91 100644
--- a/services/core/java/com/android/server/wm/SurfaceFreezer.java
+++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java
@@ -17,22 +17,21 @@
package com.android.server.wm;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.view.Surface;
+import android.util.Slog;
import android.view.SurfaceControl;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import java.util.function.Supplier;
-
/**
* This class handles "freezing" of an Animatable. The Animatable in question should implement
* Freezable.
@@ -51,16 +50,19 @@ import java.util.function.Supplier;
*/
class SurfaceFreezer {
- private final Freezable mAnimatable;
- private final WindowManagerService mWmService;
- private SurfaceControl mLeash;
+ private static final String TAG = "SurfaceFreezer";
+
+ private final @NonNull Freezable mAnimatable;
+ private final @NonNull WindowManagerService mWmService;
+ @VisibleForTesting
+ SurfaceControl mLeash;
Snapshot mSnapshot = null;
final Rect mFreezeBounds = new Rect();
/**
* @param animatable The object to animate.
*/
- SurfaceFreezer(Freezable animatable, WindowManagerService service) {
+ SurfaceFreezer(@NonNull Freezable animatable, @NonNull WindowManagerService service) {
mAnimatable = animatable;
mWmService = service;
}
@@ -70,26 +72,34 @@ class SurfaceFreezer {
* above the target surface) and then taking a snapshot and placing it over the target surface.
*
* @param startBounds The original bounds (on screen) of the surface we are snapshotting.
+ * @param relativePosition The related position of the snapshot surface to its parent.
+ * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
+ * snapshot from the {@link #mAnimatable} surface.
*/
- void freeze(SurfaceControl.Transaction t, Rect startBounds) {
+ void freeze(SurfaceControl.Transaction t, Rect startBounds, Point relativePosition,
+ @Nullable SurfaceControl freezeTarget) {
+ reset(t);
mFreezeBounds.set(startBounds);
mLeash = SurfaceAnimator.createAnimationLeash(mAnimatable, mAnimatable.getSurfaceControl(),
t, ANIMATION_TYPE_SCREEN_ROTATION, startBounds.width(), startBounds.height(),
- startBounds.left, startBounds.top, false /* hidden */,
+ relativePosition.x, relativePosition.y, false /* hidden */,
mWmService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
- SurfaceControl freezeTarget = mAnimatable.getFreezeSnapshotTarget();
+ freezeTarget = freezeTarget != null ? freezeTarget : mAnimatable.getFreezeSnapshotTarget();
if (freezeTarget != null) {
- SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBuffer(
+ SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = createSnapshotBufferInner(
freezeTarget, startBounds);
final HardwareBuffer buffer = screenshotBuffer == null ? null
: screenshotBuffer.getHardwareBuffer();
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+ // This can happen when display is not ready.
+ Slog.w(TAG, "Failed to capture screenshot for " + mAnimatable);
+ unfreeze(t);
return;
}
- mSnapshot = new Snapshot(mWmService.mSurfaceFactory, t, screenshotBuffer, mLeash);
+ mSnapshot = new Snapshot(t, screenshotBuffer, mLeash);
}
}
@@ -104,12 +114,30 @@ class SurfaceFreezer {
}
/**
+ * Used by {@link SurfaceAnimator}. This "transfers" the snapshot leash to be used for
+ * animation. By transferring the leash, this will no longer try to clean-up the leash when
+ * finished.
+ */
+ @Nullable
+ Snapshot takeSnapshotForAnimation() {
+ final Snapshot out = mSnapshot;
+ mSnapshot = null;
+ return out;
+ }
+
+ /**
* Clean-up the snapshot and remove leash. If the leash was taken, this just cleans-up the
* snapshot.
*/
void unfreeze(SurfaceControl.Transaction t) {
+ unfreezeInner(t);
+ mAnimatable.onUnfrozen();
+ }
+
+ private void unfreezeInner(SurfaceControl.Transaction t) {
if (mSnapshot != null) {
mSnapshot.cancelAnimation(t, false /* restarting */);
+ mSnapshot = null;
}
if (mLeash == null) {
return;
@@ -117,12 +145,40 @@ class SurfaceFreezer {
SurfaceControl leash = mLeash;
mLeash = null;
final boolean scheduleAnim = SurfaceAnimator.removeLeash(t, mAnimatable, leash,
- false /* destroy */);
+ true /* destroy */);
if (scheduleAnim) {
mWmService.scheduleAnimationLocked();
}
}
+ /** Resets the snapshot before taking another one if the animation hasn't been started yet. */
+ private void reset(SurfaceControl.Transaction t) {
+ // Those would have been taken by the SurfaceAnimator if the animation has been started, so
+ // we can remove the leash directly.
+ // No need to reset the mAnimatable leash, as this is called before a new animation leash is
+ // created, so another #onAnimationLeashCreated will be called.
+ if (mSnapshot != null) {
+ mSnapshot.destroy(t);
+ mSnapshot = null;
+ }
+ if (mLeash != null) {
+ t.remove(mLeash);
+ mLeash = null;
+ }
+ }
+
+ void setLayer(SurfaceControl.Transaction t, int layer) {
+ if (mLeash != null) {
+ t.setLayer(mLeash, layer);
+ }
+ }
+
+ void setRelativeLayer(SurfaceControl.Transaction t, SurfaceControl relativeTo, int layer) {
+ if (mLeash != null) {
+ t.setRelativeLayer(mLeash, relativeTo, layer);
+ }
+ }
+
boolean hasLeash() {
return mLeash != null;
}
@@ -143,21 +199,29 @@ class SurfaceFreezer {
return SurfaceControl.captureLayers(captureArgs);
}
+ @VisibleForTesting
+ SurfaceControl.ScreenshotHardwareBuffer createSnapshotBufferInner(
+ SurfaceControl target, Rect bounds) {
+ return createSnapshotBuffer(target, bounds);
+ }
+
+ @VisibleForTesting
+ GraphicBuffer createFromHardwareBufferInner(
+ SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer) {
+ return GraphicBuffer.createFromHardwareBuffer(screenshotBuffer.getHardwareBuffer());
+ }
+
class Snapshot {
private SurfaceControl mSurfaceControl;
private AnimationAdapter mAnimation;
- private SurfaceAnimator.OnAnimationFinishedCallback mFinishedCallback;
/**
* @param t Transaction to create the thumbnail in.
* @param screenshotBuffer A thumbnail or placeholder for thumbnail to initialize with.
*/
- Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t,
+ Snapshot(SurfaceControl.Transaction t,
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) {
- // We can't use a delegating constructor since we need to
- // reference this::onAnimationFinished
- GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
- screenshotBuffer.getHardwareBuffer());
+ GraphicBuffer graphicBuffer = createFromHardwareBufferInner(screenshotBuffer);
mSurfaceControl = mAnimatable.makeAnimationLeash()
.setName("snapshot anim: " + mAnimatable.toString())
@@ -194,19 +258,15 @@ class SurfaceFreezer {
* component responsible for running the animation. It runs the animation with
* {@link AnimationAdapter#startAnimation} once the hierarchy with
* the Leash has been set up.
- * @param animationFinishedCallback The callback being triggered when the animation
- * finishes.
*/
- void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type,
- @Nullable SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback) {
+ void startAnimation(SurfaceControl.Transaction t, AnimationAdapter anim, int type) {
cancelAnimation(t, true /* restarting */);
mAnimation = anim;
- mFinishedCallback = animationFinishedCallback;
if (mSurfaceControl == null) {
cancelAnimation(t, false /* restarting */);
return;
}
- mAnimation.startAnimation(mSurfaceControl, t, type, animationFinishedCallback);
+ mAnimation.startAnimation(mSurfaceControl, t, type, (typ, ani) -> { });
}
/**
@@ -218,18 +278,9 @@ class SurfaceFreezer {
void cancelAnimation(SurfaceControl.Transaction t, boolean restarting) {
final SurfaceControl leash = mSurfaceControl;
final AnimationAdapter animation = mAnimation;
- final SurfaceAnimator.OnAnimationFinishedCallback animationFinishedCallback =
- mFinishedCallback;
mAnimation = null;
- mFinishedCallback = null;
if (animation != null) {
animation.onAnimationCancelled(leash);
- if (!restarting) {
- if (animationFinishedCallback != null) {
- animationFinishedCallback.onAnimationFinished(
- ANIMATION_TYPE_APP_TRANSITION, animation);
- }
- }
}
if (!restarting) {
destroy(t);
@@ -244,5 +295,8 @@ class SurfaceFreezer {
* will be generated (but the rest of the freezing logic will still happen).
*/
@Nullable SurfaceControl getFreezeSnapshotTarget();
+
+ /** Called when the {@link #unfreeze(SurfaceControl.Transaction)} is called. */
+ void onUnfrozen();
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cc36e9e2282b..ba5e32891255 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -27,13 +27,10 @@ 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.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
-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;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.windowingModeToString;
@@ -43,7 +40,6 @@ import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
-import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
@@ -53,9 +49,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-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 android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -67,7 +60,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -84,22 +76,18 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_SHOWN;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+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;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
import static com.android.server.wm.ActivityRecord.TRANSFER_SPLASH_SCREEN_COPYING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
@@ -112,7 +100,6 @@ import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
-import static com.android.server.wm.ActivityTaskSupervisor.dumpHistoryList;
import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
@@ -123,21 +110,12 @@ import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.TaskProto.ACTIVITY_TYPE;
import static com.android.server.wm.TaskProto.AFFINITY;
import static com.android.server.wm.TaskProto.BOUNDS;
import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
-import static com.android.server.wm.TaskProto.DISPLAY_ID;
import static com.android.server.wm.TaskProto.FILLS_PARENT;
import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY;
import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS;
-import static com.android.server.wm.TaskProto.MIN_HEIGHT;
-import static com.android.server.wm.TaskProto.MIN_WIDTH;
import static com.android.server.wm.TaskProto.ORIG_ACTIVITY;
import static com.android.server.wm.TaskProto.REAL_ACTIVITY;
import static com.android.server.wm.TaskProto.RESIZE_MODE;
@@ -145,15 +123,13 @@ import static com.android.server.wm.TaskProto.RESUMED_ACTIVITY;
import static com.android.server.wm.TaskProto.ROOT_TASK_ID;
import static com.android.server.wm.TaskProto.SURFACE_HEIGHT;
import static com.android.server.wm.TaskProto.SURFACE_WIDTH;
-import static com.android.server.wm.TaskProto.WINDOW_CONTAINER;
+import static com.android.server.wm.TaskProto.TASK_FRAGMENT;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.dipToPixel;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static java.lang.Integer.MAX_VALUE;
@@ -170,14 +146,8 @@ import android.app.AppGlobals;
import android.app.IActivityController;
import android.app.PictureInPictureParams;
import android.app.RemoteAction;
-import android.app.ResultInfo;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
-import android.app.servertransaction.ActivityResultItem;
-import android.app.servertransaction.ClientTransaction;
-import android.app.servertransaction.NewIntentItem;
-import android.app.servertransaction.PauseActivityItem;
-import android.app.servertransaction.ResumeActivityItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -185,6 +155,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
@@ -194,6 +165,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -208,10 +180,11 @@ import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
+import android.view.InsetsState;
import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.TaskTransitionSpec;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
import android.window.ITaskOrganizer;
@@ -246,23 +219,21 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
-class Task extends WindowContainer<WindowContainer> {
+/**
+ * {@link Task} is a TaskFragment that can contain a group of activities to perform a certain job.
+ * Activities of the same task affinities usually group in the same {@link Task}. A {@link Task}
+ * can also be an entity that showing in the Recents Screen for a job that user interacted with.
+ * A {@link Task} can also contain other {@link Task}s.
+ */
+class Task extends TaskFragment {
private static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_ATM;
- static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
- private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
static final String TAG_TASKS = TAG + POSTFIX_TASKS;
- private static final String TAG_APP = TAG + POSTFIX_APP;
static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
- private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
- private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
- private static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
- private static final String TAG_STATES = TAG + POSTFIX_STATES;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
@@ -305,10 +276,6 @@ class Task extends WindowContainer<WindowContainer> {
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";
- // Set to false to disable the preview that is shown while a new activity
- // is being started.
- private static final boolean SHOW_APP_STARTING_PREVIEW = true;
-
// How long to wait for all background Activities to redraw following a call to
// convertToTranslucent().
private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
@@ -320,7 +287,6 @@ class Task extends WindowContainer<WindowContainer> {
//ActivityTrigger
static final ActivityTrigger mActivityTrigger = new ActivityTrigger();
- static final int INVALID_MIN_SIZE = -1;
private float mShadowRadius = 0;
/**
@@ -340,36 +306,6 @@ class Task extends WindowContainer<WindowContainer> {
// Do not move the root task as a part of reparenting
static final int REPARENT_LEAVE_ROOT_TASK_IN_PLACE = 2;
- @IntDef(prefix = {"TASK_VISIBILITY"}, value = {
- TASK_VISIBILITY_VISIBLE,
- TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
- TASK_VISIBILITY_INVISIBLE,
- })
- @interface TaskVisibility {}
-
- /** Task is visible. No other tasks on top that fully or partially occlude it. */
- static final int TASK_VISIBILITY_VISIBLE = 0;
-
- /** Task is partially occluded by other translucent task(s) on top of it. */
- static final int TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
-
- /** Task is completely invisible. */
- static final int TASK_VISIBILITY_INVISIBLE = 2;
-
- enum ActivityState {
- INITIALIZING,
- STARTED,
- RESUMED,
- PAUSING,
- PAUSED,
- STOPPING,
- STOPPED,
- FINISHING,
- DESTROYING,
- DESTROYED,
- RESTARTING_PROCESS
- }
-
public BoostFramework mPerf = null;
// The topmost Activity passed to convertToTranslucent(). When non-null it means we are
@@ -392,6 +328,11 @@ class Task extends WindowContainer<WindowContainer> {
*/
boolean mInResumeTopActivity = false;
+ /**
+ * Used to identify if the activity that is installed from device's system image.
+ */
+ boolean mIsEffectivelySystemApp;
+
int mCurrentUser;
String affinity; // The affinity name for this task, or null; may change identity.
@@ -453,7 +394,6 @@ class Task extends WindowContainer<WindowContainer> {
CharSequence lastDescription; // Last description captured for this item.
- Task mAdjacentTask; // Task adjacent to this one.
int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
Task mPrevAffiliate; // previous task in affiliated chain.
int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
@@ -465,21 +405,12 @@ class Task extends WindowContainer<WindowContainer> {
String mCallingPackage;
String mCallingFeatureId;
- private final Rect mTmpStableBounds = new Rect();
- private final Rect mTmpNonDecorBounds = new Rect();
- private final Rect mTmpBounds = new Rect();
- private final Rect mTmpInsets = new Rect();
- private final Rect mTmpFullBounds = new Rect();
private static final Rect sTmpBounds = new Rect();
// Last non-fullscreen bounds the task was launched in or resized to.
// The information is persisted and used to determine the appropriate root task to launch the
// task into on restore.
Rect mLastNonFullscreenBounds = null;
- // Minimal width and height of this task when it's resizeable. -1 means it should use the
- // default minimal width/height.
- int mMinWidth;
- int mMinHeight;
// The surface transition of the target when recents animation is finished.
// This is originally introduced to carry out the current surface control position and window
@@ -504,10 +435,6 @@ class Task extends WindowContainer<WindowContainer> {
/** Used by fillTaskInfo */
final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport();
- final ActivityTaskManagerService mAtmService;
- final ActivityTaskSupervisor mTaskSupervisor;
- final RootWindowContainer mRootWindowContainer;
-
/* Unique identifier for this task. */
final int mTaskId;
/* User for which this task was created. */
@@ -566,9 +493,7 @@ class Task extends WindowContainer<WindowContainer> {
// root task moves and we in fact do so when moving from full screen to pinned.
private boolean mPreserveNonFloatingState = false;
- private Dimmer mDimmer = new Dimmer(this);
private final Rect mTmpDimBoundsRect = new Rect();
- private final Point mLastSurfaceSize = new Point();
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -578,29 +503,6 @@ class Task extends WindowContainer<WindowContainer> {
/** ActivityRecords that are exiting, but still on screen for animations. */
final ArrayList<ActivityRecord> mExitingActivities = new ArrayList<>();
- /**
- * When we are in the process of pausing an activity, before starting the
- * next one, this variable holds the activity that is currently being paused.
- *
- * Only set at leaf tasks.
- */
- @Nullable
- private ActivityRecord mPausingActivity = null;
-
- /**
- * This is the last activity that we put into the paused state. This is
- * used to determine if we need to do an activity transition while sleeping,
- * when we normally hold the top activity paused.
- */
- ActivityRecord mLastPausedActivity = null;
-
- /**
- * Current activity that is resumed, or null if there is none.
- * Only set at leaf tasks.
- */
- @Nullable
- private ActivityRecord mResumedActivity = null;
-
private boolean mForceShowForAllUsers;
/** When set, will force the task to report as invisible. */
@@ -651,121 +553,6 @@ class Task extends WindowContainer<WindowContainer> {
}
private static final ResetTargetTaskHelper sResetTargetTaskHelper = new ResetTargetTaskHelper();
- private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
- new EnsureActivitiesVisibleHelper(this);
- private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
- new EnsureVisibleActivitiesConfigHelper();
- private class EnsureVisibleActivitiesConfigHelper {
- private boolean mUpdateConfig;
- private boolean mPreserveWindow;
- private boolean mBehindFullscreen;
-
- void reset(boolean preserveWindow) {
- mPreserveWindow = preserveWindow;
- mUpdateConfig = false;
- mBehindFullscreen = false;
- }
-
- void process(ActivityRecord start, boolean preserveWindow) {
- if (start == null || !start.mVisibleRequested) {
- return;
- }
- reset(preserveWindow);
-
- final PooledFunction f = PooledLambda.obtainFunction(
- EnsureVisibleActivitiesConfigHelper::processActivity, this,
- PooledLambda.__(ActivityRecord.class));
- forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/);
- f.recycle();
-
- if (mUpdateConfig) {
- // Ensure the resumed state of the focus activity if we updated the configuration of
- // any activity.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- }
-
- boolean processActivity(ActivityRecord r) {
- mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
- mBehindFullscreen |= r.occludesParent();
- return mBehindFullscreen;
- }
- }
-
- private final CheckBehindFullscreenActivityHelper mCheckBehindFullscreenActivityHelper =
- new CheckBehindFullscreenActivityHelper();
- private class CheckBehindFullscreenActivityHelper {
- private boolean mAboveTop;
- private boolean mBehindFullscreenActivity;
- private ActivityRecord mToCheck;
- private Consumer<ActivityRecord> mHandleBehindFullscreenActivity;
- private boolean mHandlingOccluded;
-
- private void reset(ActivityRecord toCheck,
- Consumer<ActivityRecord> handleBehindFullscreenActivity) {
- mToCheck = toCheck;
- mHandleBehindFullscreenActivity = handleBehindFullscreenActivity;
- mAboveTop = true;
- mBehindFullscreenActivity = false;
-
- if (!shouldBeVisible(null)) {
- // The root task is not visible, so no activity in it should be displaying a
- // starting window. Mark all activities below top and behind fullscreen.
- mAboveTop = false;
- mBehindFullscreenActivity = true;
- }
-
- mHandlingOccluded = mToCheck == null && mHandleBehindFullscreenActivity != null;
- }
-
- boolean process(ActivityRecord toCheck,
- Consumer<ActivityRecord> handleBehindFullscreenActivity) {
- reset(toCheck, handleBehindFullscreenActivity);
-
- if (!mHandlingOccluded && mBehindFullscreenActivity) {
- return true;
- }
-
- final ActivityRecord topActivity = topRunningActivity();
- final PooledFunction f = PooledLambda.obtainFunction(
- CheckBehindFullscreenActivityHelper::processActivity, this,
- PooledLambda.__(ActivityRecord.class), topActivity);
- forAllActivities(f);
- f.recycle();
-
- return mBehindFullscreenActivity;
- }
-
- /** Returns {@code true} to stop the outer loop and indicate the result is computed. */
- private boolean processActivity(ActivityRecord r, ActivityRecord topActivity) {
- if (mAboveTop) {
- if (r == topActivity) {
- if (r == mToCheck) {
- // It is the top activity in a visible root task.
- mBehindFullscreenActivity = false;
- return true;
- }
- mAboveTop = false;
- }
- mBehindFullscreenActivity |= r.occludesParent();
- return false;
- }
-
- if (mHandlingOccluded) {
- // Iterating through all occluded activities.
- if (mBehindFullscreenActivity) {
- mHandleBehindFullscreenActivity.accept(r);
- }
- } else if (r == mToCheck) {
- return true;
- } else if (mBehindFullscreenActivity) {
- // It is occluded before {@param toCheck} is found.
- return true;
- }
- mBehindFullscreenActivity |= r.occludesParent();
- return false;
- }
- }
private final FindRootHelper mFindRootHelper = new FindRootHelper();
private class FindRootHelper {
@@ -795,11 +582,24 @@ class Task extends WindowContainer<WindowContainer> {
if (r.finishing) return false;
- // Set this as the candidate root since it isn't finishing.
- mRoot = r;
+ if (mRoot == null || mRoot.finishing) {
+ // Set this as the candidate root since it isn't finishing.
+ mRoot = r;
+ }
+
+ final int uid = mRoot == r ? effectiveUid : r.info.applicationInfo.uid;
+ if (ignoreRelinquishIdentity
+ || (mRoot.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0
+ || (mRoot.info.applicationInfo.uid != Process.SYSTEM_UID
+ && !mRoot.info.applicationInfo.isSystemApp()
+ && mRoot.info.applicationInfo.uid != uid)) {
+ // No need to relinquish identity, end search.
+ return true;
+ }
- // Only end search if we are ignore relinquishing identity or we are not relinquishing.
- return ignoreRelinquishIdentity || (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+ // Relinquish to next activity
+ mRoot = r;
+ return false;
}
}
@@ -819,28 +619,6 @@ class Task extends WindowContainer<WindowContainer> {
// false.
private boolean mDeferTaskAppear;
- /**
- * Forces this task to be unorganized. Currently it is used for deferring the control of
- * organizer when windowing mode is changing from PiP to fullscreen with orientation change.
- * It is true only during Task#setWindowingMode ~ DisplayRotation#continueRotation.
- *
- * TODO(b/179235349): Remove this field by making surface operations from task organizer sync
- * with display rotation.
- */
- private boolean mForceNotOrganized;
-
- /**
- * This task was created by the task organizer which has the following implementations.
- * <ul>
- * <lis>The task won't be removed when it is empty. Removal has to be an explicit request
- * from the task organizer.</li>
- * <li>Unlike other non-root tasks, it's direct children are visible to the task
- * organizer for ordering purposes.</li>
- * </ul>
- */
- @VisibleForTesting
- boolean mCreatedByOrganizer;
-
// Tracking cookie for the creation of this task.
IBinder mLaunchCookie;
@@ -868,11 +646,8 @@ class Task extends WindowContainer<WindowContainer> {
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor,
boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear,
boolean _removeWithTaskOrganizer) {
- super(atmService.mWindowManager);
+ super(atmService, null /* fragmentToken */, _createdByOrganizer, false /* isEmbedded */);
- mAtmService = atmService;
- mTaskSupervisor = atmService.mTaskSupervisor;
- mRootWindowContainer = mAtmService.mRootWindowContainer;
mTaskId = _taskId;
mUserId = _userId;
mResizeMode = resizeMode;
@@ -885,7 +660,6 @@ class Task extends WindowContainer<WindowContainer> {
: new PersistedTaskSnapshotData();
// Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
setOrientation(SCREEN_ORIENTATION_UNSET);
- mRemoteToken = new RemoteToken(this);
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
@@ -923,7 +697,6 @@ class Task extends WindowContainer<WindowContainer> {
mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
mCurrentUser = mAtmService.mAmInternal.getCurrentUserId();
- mCreatedByOrganizer = _createdByOrganizer;
mLaunchCookie = _launchCookie;
mDeferTaskAppear = _deferTaskAppear;
mRemoveWithTaskOrganizer = _removeWithTaskOrganizer;
@@ -952,13 +725,13 @@ class Task extends WindowContainer<WindowContainer> {
return this;
}
- private void cleanUpResourcesForDestroy(ConfigurationContainer oldParent) {
+ private void cleanUpResourcesForDestroy(WindowContainer<?> oldParent) {
if (hasChild()) {
return;
}
// This task is going away, so save the last state if necessary.
- saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent());
+ saveLaunchingStateIfNeeded(oldParent.getDisplayContent());
// TODO: VI what about activity?
final boolean isVoiceSession = voiceSession != null;
@@ -968,7 +741,7 @@ class Task extends WindowContainer<WindowContainer> {
} catch (RemoteException e) {
}
}
- if (autoRemoveFromRecents() || isVoiceSession) {
+ if (autoRemoveFromRecents(oldParent.asTaskFragment()) || isVoiceSession) {
// Task creator asked to remove this when done, or this task was a voice
// interaction, so it should not remain on the recent tasks list.
mTaskSupervisor.mRecentTasks.remove(this);
@@ -1251,12 +1024,19 @@ class Task extends WindowContainer<WindowContainer> {
* @param info The activity info which could be different from {@code r.info} if set.
*/
void setIntent(ActivityRecord r, @Nullable Intent intent, @Nullable ActivityInfo info) {
- if (this.intent == null || !mNeverRelinquishIdentity) {
+ boolean updateIdentity = false;
+ if (this.intent == null) {
+ updateIdentity = true;
+ } else if (!mNeverRelinquishIdentity) {
+ final ActivityInfo activityInfo = info != null ? info : r.info;
+ updateIdentity = (effectiveUid == Process.SYSTEM_UID || mIsEffectivelySystemApp
+ || effectiveUid == activityInfo.applicationInfo.uid);
+ }
+ if (updateIdentity) {
mCallingUid = r.launchedFromUid;
mCallingPackage = r.launchedFromPackage;
mCallingFeatureId = r.launchedFromFeatureId;
setIntent(intent != null ? intent : r.intent, info != null ? info : r.info);
- return;
}
setLockTaskAuth(r);
}
@@ -1274,6 +1054,7 @@ class Task extends WindowContainer<WindowContainer> {
rootAffinity = affinity;
}
effectiveUid = info.applicationInfo.uid;
+ mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
stringName = null;
if (info.targetActivity == null) {
@@ -1372,7 +1153,11 @@ class Task extends WindowContainer<WindowContainer> {
if (inMultiWindowMode() || !hasChild()) return false;
if (intent != null) {
final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
- return intent != null && (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
+ final Task task = getDisplayArea() != null ? getDisplayArea().getRootHomeTask() : null;
+ final boolean isLockTaskModeViolation = task != null
+ && mAtmService.getLockTaskController().isLockTaskModeViolation(task);
+ return (intent.getFlags() & returnHomeFlags) == returnHomeFlags
+ && !isLockTaskModeViolation;
}
final Task bottomTask = getBottomMostTask();
return bottomTask != this && bottomTask.returnsToHomeRootTask();
@@ -1389,11 +1174,11 @@ class Task extends WindowContainer<WindowContainer> {
}
@Override
- void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- final DisplayContent display = newParent != null
- ? ((WindowContainer) newParent).getDisplayContent() : null;
- final DisplayContent oldDisplay = oldParent != null
- ? ((WindowContainer) oldParent).getDisplayContent() : null;
+ void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) {
+ final WindowContainer<?> newParent = (WindowContainer<?>) rawNewParent;
+ final WindowContainer<?> oldParent = (WindowContainer<?>) rawOldParent;
+ final DisplayContent display = newParent != null ? newParent.getDisplayContent() : null;
+ final DisplayContent oldDisplay = oldParent != null ? oldParent.getDisplayContent() : null;
mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY;
@@ -1434,7 +1219,7 @@ class Task extends WindowContainer<WindowContainer> {
}
if (oldParent != null) {
- final Task oldParentTask = ((WindowContainer) oldParent).asTask();
+ final Task oldParentTask = oldParent.asTask();
if (oldParentTask != null) {
final PooledConsumer c = PooledLambda.obtainConsumer(
Task::cleanUpActivityReferences, oldParentTask,
@@ -1452,6 +1237,12 @@ class Task extends WindowContainer<WindowContainer> {
}
if (newParent != null) {
+ // Surface of Task that will not be organized should be shown by default.
+ // See Task#showSurfaceOnCreation
+ if (!mCreatedByOrganizer && !canBeOrganized()) {
+ getSyncTransaction().show(mSurfaceControl);
+ }
+
// TODO: Ensure that this is actually necessary here
// Notify the voice session if required
if (voiceSession != null) {
@@ -1476,64 +1267,62 @@ class Task extends WindowContainer<WindowContainer> {
mRootWindowContainer.updateUIDsPresentOnDisplay();
}
- void cleanUpActivityReferences(ActivityRecord r) {
- // mPausingActivity is set at leaf task
- if (mPausingActivity != null && mPausingActivity == r) {
- mPausingActivity = null;
- }
-
- if (mResumedActivity != null && mResumedActivity == r) {
- setResumedActivity(null, "cleanUpActivityReferences");
- }
-
- final WindowContainer parent = getParent();
- if (parent != null && parent.asTask() != null) {
- parent.asTask().cleanUpActivityReferences(r);
- return;
+ @Override
+ @Nullable
+ ActivityRecord getTopResumedActivity() {
+ if (!isLeafTask()) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ ActivityRecord resumedActivity = mChildren.get(i).asTask().getTopResumedActivity();
+ if (resumedActivity != null) {
+ return resumedActivity;
+ }
+ }
}
- r.removeTimeouts();
- mExitingActivities.remove(r);
- }
- /** @return the currently resumed activity. */
- ActivityRecord getResumedActivity() {
- if (isLeafTask()) {
- return mResumedActivity;
+ final ActivityRecord taskResumedActivity = getResumedActivity();
+ ActivityRecord topResumedActivity = null;
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ if (child.asTaskFragment() != null) {
+ topResumedActivity = child.asTaskFragment().getTopResumedActivity();
+ } else if (taskResumedActivity != null
+ && child.asActivityRecord() == taskResumedActivity) {
+ topResumedActivity = taskResumedActivity;
+ }
+ if (topResumedActivity != null) {
+ return topResumedActivity;
+ }
}
-
- final Task task = getTask(t -> t.mResumedActivity != null, true /* traverseTopToBottom */);
- return task != null ? task.mResumedActivity : null;
- }
-
- @VisibleForTesting
- void setPausingActivity(ActivityRecord pausing) {
- mPausingActivity = pausing;
+ return null;
}
- /**
- * @return the currently pausing activity of this task or the topmost pausing activity of the
- * child tasks
- */
- ActivityRecord getPausingActivity() {
- if (isLeafTask()) {
- return mPausingActivity;
+ @Override
+ @Nullable
+ ActivityRecord getTopPausingActivity() {
+ if (!isLeafTask()) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ ActivityRecord pausingActivity = mChildren.get(i).asTask().getTopPausingActivity();
+ if (pausingActivity != null) {
+ return pausingActivity;
+ }
+ }
}
- final Task task = getTask(t -> t.mPausingActivity != null, true /* traverseTopToBottom */);
- return task != null ? task.mPausingActivity : null;
- }
-
- void setResumedActivity(ActivityRecord r, String reason) {
- warnForNonLeafTask("setResumedActivity");
- if (mResumedActivity == r) {
- return;
+ final ActivityRecord taskPausingActivity = getPausingActivity();
+ ActivityRecord topPausingActivity = null;
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ if (child.asTaskFragment() != null) {
+ topPausingActivity = child.asTaskFragment().getTopPausingActivity();
+ } else if (taskPausingActivity != null
+ && child.asActivityRecord() == taskPausingActivity) {
+ topPausingActivity = taskPausingActivity;
+ }
+ if (topPausingActivity != null) {
+ return topPausingActivity;
+ }
}
-
- if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK,
- "setResumedActivity task:" + this + " + from: "
- + mResumedActivity + " to:" + r + " reason:" + reason);
- mResumedActivity = r;
- mTaskSupervisor.updateTopResumedActivityIfNeeded();
+ return null;
}
void updateTaskMovement(boolean toTop, int position) {
@@ -1572,11 +1361,6 @@ class Task extends WindowContainer<WindowContainer> {
mTaskId, mUserId);
}
- void setAdjacentTask(Task adjacent) {
- mAdjacentTask = adjacent;
- adjacent.mAdjacentTask = this;
- }
-
void setTaskToAffiliateWith(Task taskToAffiliateWith) {
closeRecentsChain();
mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
@@ -1622,14 +1406,6 @@ class Task extends WindowContainer<WindowContainer> {
return mFindRootHelper.findRoot(ignoreRelinquishIdentity, setToBottomIfNone);
}
- ActivityRecord getTopNonFinishingActivity() {
- return getTopNonFinishingActivity(true /* includeOverlays */);
- }
-
- ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
- return getTopActivity(false /*includeFinishing*/, includeOverlays);
- }
-
ActivityRecord topRunningActivityLocked() {
if (getParent() == null) {
return null;
@@ -1656,14 +1432,6 @@ class Task extends WindowContainer<WindowContainer> {
window.getBaseType() == TYPE_APPLICATION_STARTING) != null);
}
- ActivityRecord topActivityWithStartingWindow() {
- if (getParent() == null) {
- return null;
- }
- return getActivity((r) -> r.mStartingWindowState == STARTING_WINDOW_SHOWN
- && r.okToShowLocked());
- }
-
/**
* Return the number of running activities, and the number of non-finishing/initializing
* activities in the provided {@param reportOut} respectively.
@@ -1685,22 +1453,7 @@ class Task extends WindowContainer<WindowContainer> {
}
@Override
- public int getActivityType() {
- final int applicationType = super.getActivityType();
- if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
- return applicationType;
- }
- return getTopChild().getActivityType();
- }
-
- @Override
void addChild(WindowContainer child, int index) {
- // If this task had any child before we added this one.
- boolean hadChild = hasChild();
- // getActivityType() looks at the top child, so we need to read the type before adding
- // a new child in case the new child is on top and UNDEFINED.
- final int activityType = getActivityType();
-
index = getAdjustedChildPosition(child, index);
super.addChild(child, index);
@@ -1716,13 +1469,20 @@ class Task extends WindowContainer<WindowContainer> {
// now that this record is in a new task.
mRootWindowContainer.updateUIDsPresentOnDisplay();
- final ActivityRecord r = child.asActivityRecord();
- if (r == null) return;
+ // Only pass minimum dimensions for pure TaskFragment. Task's minimum dimensions must be
+ // passed from Task constructor.
+ final TaskFragment childTaskFrag = child.asTaskFragment();
+ if (childTaskFrag != null && childTaskFrag.asTask() == null) {
+ childTaskFrag.setMinDimensions(mMinWidth, mMinHeight);
+ }
+ }
- r.inHistory = true;
+ /** Called when an {@link ActivityRecord} is added as a descendant */
+ void onDescendantActivityAdded(boolean hadActivity, int activityType, ActivityRecord r) {
+ warnForNonLeafTask("onDescendantActivityAdded");
// Only set this based on the first activity
- if (!hadChild) {
+ if (!hadActivity) {
if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) {
// Normally non-standard activity type for the activity record will be set when the
// object is created, however we delay setting the standard application type until
@@ -1746,10 +1506,6 @@ class Task extends WindowContainer<WindowContainer> {
updateEffectiveIntent();
}
- void addChild(ActivityRecord r) {
- addChild(r, Integer.MAX_VALUE /* add on top */);
- }
-
@Override
void removeChild(WindowContainer child) {
removeChild(child, "removeChild");
@@ -1769,7 +1525,7 @@ class Task extends WindowContainer<WindowContainer> {
if (DEBUG_TASK_MOVEMENT) {
Slog.d(TAG_WM, "removeChild: child=" + r + " reason=" + reason);
}
- super.removeChild(r);
+ super.removeChild(r, false /* removeSelfIfPossible */);
if (inPinnedWindowingMode()) {
// We normally notify listeners of task stack changes on pause, however root pinned task
@@ -1799,7 +1555,10 @@ class Task extends WindowContainer<WindowContainer> {
// Remove entire task if it doesn't have any activity left and it isn't marked for reuse
// or created by task organizer.
if (!isRootTask()) {
- getRootTask().removeChild(this, reason);
+ final WindowContainer<?> parent = getParent();
+ if (parent != null) {
+ parent.asTaskFragment().removeChild(this);
+ }
}
EventLogTags.writeWmTaskRemoved(mTaskId,
"removeChild:" + reason + " last r=" + r + " in t=" + this);
@@ -1831,11 +1590,12 @@ class Task extends WindowContainer<WindowContainer> {
return count > 0;
}
- private boolean autoRemoveFromRecents() {
+ private boolean autoRemoveFromRecents(TaskFragment oldParentFragment) {
// We will automatically remove the task either if it has explicitly asked for
// this, or it is empty and has never contained an activity that got shown to
- // the user.
- return autoRemoveRecents || (!hasChild() && !getHasBeenVisible());
+ // the user, or it was being embedded in another Task.
+ return autoRemoveRecents || (!hasChild() && !getHasBeenVisible()
+ || (oldParentFragment != null && oldParentFragment.isEmbedded()));
}
private void clearPinnedTaskIfNeed() {
@@ -1859,9 +1619,15 @@ class Task extends WindowContainer<WindowContainer> {
} else {
forAllActivities((r) -> {
if (r.finishing) return;
- // TODO: figure-out how to avoid object creation due to capture of reason variable.
- r.finishIfPossible(Activity.RESULT_CANCELED,
- null /* resultData */, null /* resultGrants */, reason, false /* oomAdj */);
+ // Prevent the transition from being executed too early if the top activity is
+ // resumed but the mVisibleRequested of any other activity is true, the transition
+ // should wait until next activity resumed.
+ if (r.isState(RESUMED) || (r.isVisible()
+ && !mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CLOSE))) {
+ r.finishIfPossible(reason, false /* oomAdj */);
+ } else {
+ r.destroyIfPossible(reason);
+ }
});
}
}
@@ -1995,32 +1761,6 @@ class Task extends WindowContainer<WindowContainer> {
&& supportsMultiWindowInDisplayArea(tda);
}
- boolean supportsMultiWindow() {
- return supportsMultiWindowInDisplayArea(getDisplayArea());
- }
-
- /**
- * @return whether this task supports multi-window if it is in the given
- * {@link TaskDisplayArea}.
- */
- boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
- if (!mAtmService.mSupportsMultiWindow) {
- return false;
- }
- if (tda == null) {
- Slog.w(TAG_TASKS, "Can't find TaskDisplayArea to determine support for multi"
- + " window. Task id=" + mTaskId + " attached=" + isAttached());
- return false;
- }
-
- if (!isResizeable() && !tda.supportsNonResizableMultiWindow()) {
- // Not support non-resizable in multi window.
- return false;
- }
-
- return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight);
- }
-
/**
* Check whether this task can be launched on the specified display.
*
@@ -2156,60 +1896,6 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds,
- @NonNull Configuration parentConfig) {
- int minWidth = mMinWidth;
- int minHeight = mMinHeight;
- // If the task has no requested minimal size, we'd like to enforce a minimal size
- // so that the user can not render the task too small to manipulate. We don't need
- // to do this for the root pinned task as the bounds are controlled by the system.
- if (!inPinnedWindowingMode()) {
- final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
- final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
- final int defaultMinSize = (int) (defaultMinSizeDp * density);
-
- if (minWidth == INVALID_MIN_SIZE) {
- minWidth = defaultMinSize;
- }
- if (minHeight == INVALID_MIN_SIZE) {
- minHeight = defaultMinSize;
- }
- }
- if (bounds.isEmpty()) {
- // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they
- // do, we can just skip.
- final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
- if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) {
- return;
- }
- bounds.set(parentBounds);
- }
- final boolean adjustWidth = minWidth > bounds.width();
- final boolean adjustHeight = minHeight > bounds.height();
- if (!(adjustWidth || adjustHeight)) {
- return;
- }
-
- if (adjustWidth) {
- if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) {
- bounds.left = bounds.right - minWidth;
- } else {
- // Either left bounds match, or neither match, or the previous bounds were
- // fullscreen and we default to keeping left.
- bounds.right = bounds.left + minWidth;
- }
- }
- if (adjustHeight) {
- if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) {
- bounds.top = bounds.bottom - minHeight;
- } else {
- // Either top bounds match, or neither match, or the previous bounds were
- // fullscreen and we default to keeping top.
- bounds.bottom = bounds.top + minHeight;
- }
- }
- }
-
void setLastNonFullscreenBounds(Rect bounds) {
if (mLastNonFullscreenBounds == null) {
mLastNonFullscreenBounds = new Rect(bounds);
@@ -2218,32 +1904,6 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- /**
- * This should be called when an child activity changes state. This should only
- * be called from
- * {@link ActivityRecord#setState(ActivityState, String)} .
- * @param record The {@link ActivityRecord} whose state has changed.
- * @param state The new state.
- * @param reason The reason for the change.
- */
- void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
- warnForNonLeafTask("onActivityStateChanged");
- if (record == mResumedActivity && state != RESUMED) {
- setResumedActivity(null, reason + " - onActivityStateChanged");
- }
-
- if (state == RESUMED) {
- if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
- Slog.v(TAG_ROOT_TASK, "set resumed activity to:" + record + " reason:" + reason);
- }
- setResumedActivity(record, reason + " - onActivityStateChanged");
- if (record == mRootWindowContainer.getTopResumedActivity()) {
- mAtmService.setResumedActivityUncheckLocked(record, reason);
- }
- mTaskSupervisor.mRecentTasks.add(record.getTask());
- }
- }
-
private void onConfigurationChangedInner(Configuration newParentConfig) {
// Check if the new configuration supports persistent bounds (eg. is Freeform) and if so
// restore the last recorded non-fullscreen bounds.
@@ -2291,19 +1951,17 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- if (pipChanging) {
- // If the top activity is using fixed rotation, it should be changing from PiP to
- // fullscreen with display orientation change. Do not notify fullscreen task organizer
- // because the restoration of task surface and the transformation of activity surface
- // need to be done synchronously.
+ if (pipChanging && wasInPictureInPicture) {
+ // If the top activity is changing from PiP to fullscreen with fixed rotation,
+ // clear the crop and rotation matrix of task because fixed rotation will handle
+ // the transformation on activity level. This also avoids flickering caused by the
+ // latency of fullscreen task organizer configuring the surface.
final ActivityRecord r = topRunningActivity();
if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) {
- mForceNotOrganized = true;
+ getSyncTransaction().setWindowCrop(mSurfaceControl, null)
+ .setCornerRadius(mSurfaceControl, 0f)
+ .setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]);
}
- } else {
- // If the display orientation change is done, let the corresponding task organizer take
- // back the control of this task.
- mForceNotOrganized = false;
}
saveLaunchingStateIfNeeded();
@@ -2362,14 +2020,7 @@ class Task extends WindowContainer<WindowContainer> {
taskDisplayArea.onRootTaskWindowingModeChanged(this);
}
- if (mDisplayContent == null) {
- return;
- }
-
- // Use override windowing mode to prevent extra bounds changes if inheriting the mode.
- final int overrideWindowingMode = getRequestedOverrideWindowingMode();
- if (overrideWindowingMode != WINDOWING_MODE_PINNED
- && !getRequestedOverrideBounds().isEmpty()) {
+ if (!isOrganized() && !getRequestedOverrideBounds().isEmpty() && mDisplayContent != null) {
// If the parent (display) has rotated, rotate our bounds to best-fit where their
// bounds were on the pre-rotated display.
final int newRotation = getWindowConfiguration().getRotation();
@@ -2388,21 +2039,158 @@ class Task extends WindowContainer<WindowContainer> {
}
}
+ void resolveLeafTaskOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) {
+ if (!isLeafTask()) {
+ return;
+ }
+
+ int windowingMode =
+ getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
+ }
+ // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old
+ // mode that may cause the bounds to be miscalculated, e.g. letterboxed.
+ getConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+ Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds();
+
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // Use empty bounds to indicate "fill parent".
+ outOverrideBounds.setEmpty();
+ // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
+ // the parent or display is smaller than the size, the content may be cropped.
+ return;
+ }
+
+ adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ computeFreeformBounds(outOverrideBounds, newParentConfig);
+ return;
+ }
+ }
+
+ void adjustForMinimalTaskDimensions(@NonNull Rect bounds, @NonNull Rect previousBounds,
+ @NonNull Configuration parentConfig) {
+ int minWidth = mMinWidth;
+ int minHeight = mMinHeight;
+ // If the task has no requested minimal size, we'd like to enforce a minimal size
+ // so that the user can not render the task fragment too small to manipulate. We don't need
+ // to do this for the root pinned task as the bounds are controlled by the system.
+ if (!inPinnedWindowingMode()) {
+ final int defaultMinSizeDp = mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp;
+ final float density = (float) parentConfig.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ final int defaultMinSize = (int) (defaultMinSizeDp * density);
+
+ if (minWidth == INVALID_MIN_SIZE) {
+ minWidth = defaultMinSize;
+ }
+ if (minHeight == INVALID_MIN_SIZE) {
+ minHeight = defaultMinSize;
+ }
+ }
+ if (bounds.isEmpty()) {
+ // If inheriting parent bounds, check if parent bounds adhere to minimum size. If they
+ // do, we can just skip.
+ final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
+ if (parentBounds.width() >= minWidth && parentBounds.height() >= minHeight) {
+ return;
+ }
+ bounds.set(parentBounds);
+ }
+ final boolean adjustWidth = minWidth > bounds.width();
+ final boolean adjustHeight = minHeight > bounds.height();
+ if (!(adjustWidth || adjustHeight)) {
+ return;
+ }
+
+ if (adjustWidth) {
+ if (!previousBounds.isEmpty() && bounds.right == previousBounds.right) {
+ bounds.left = bounds.right - minWidth;
+ } else {
+ // Either left bounds match, or neither match, or the previous bounds were
+ // fullscreen and we default to keeping left.
+ bounds.right = bounds.left + minWidth;
+ }
+ }
+ if (adjustHeight) {
+ if (!previousBounds.isEmpty() && bounds.bottom == previousBounds.bottom) {
+ bounds.top = bounds.bottom - minHeight;
+ } else {
+ // Either top bounds match, or neither match, or the previous bounds were
+ // fullscreen and we default to keeping top.
+ bounds.bottom = bounds.top + minHeight;
+ }
+ }
+ }
+
+ /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
+ private void computeFreeformBounds(@NonNull Rect outBounds,
+ @NonNull Configuration newParentConfig) {
+ // by policy, make sure the window remains within parent somewhere
+ final float density =
+ ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
+ final Rect parentBounds =
+ new Rect(newParentConfig.windowConfiguration.getBounds());
+ final DisplayContent display = getDisplayContent();
+ if (display != null) {
+ // If a freeform window moves below system bar, there is no way to move it again
+ // by touch. Because its caption is covered by system bar. So we exclude them
+ // from root task bounds. and then caption will be shown inside stable area.
+ final Rect stableBounds = new Rect();
+ display.getStableRect(stableBounds);
+ parentBounds.intersect(stableBounds);
+ }
+
+ fitWithinBounds(outBounds, parentBounds,
+ (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
+ (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
+
+ // Prevent to overlap caption with stable insets.
+ final int offsetTop = parentBounds.top - outBounds.top;
+ if (offsetTop > 0) {
+ outBounds.offset(0, offsetTop);
+ }
+ }
+
/**
- * Initializes a change transition. See {@link SurfaceFreezer} for more information.
+ * Adjusts bounds to stay within root task bounds.
+ *
+ * Since bounds might be outside of root task bounds, this method tries to move the bounds in
+ * a way that keep them unchanged, but be contained within the root task bounds.
+ *
+ * @param bounds Bounds to be adjusted.
+ * @param rootTaskBounds Bounds within which the other bounds should remain.
+ * @param overlapPxX The amount of px required to be visible in the X dimension.
+ * @param overlapPxY The amount of px required to be visible in the Y dimension.
*/
- private void initializeChangeTransition(Rect startBounds) {
- mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
- mDisplayContent.mChangingContainers.add(this);
+ private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX,
+ int overlapPxY) {
+ if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) {
+ return;
+ }
- mSurfaceFreezer.freeze(getPendingTransaction(), startBounds);
+ // For each side of the parent (eg. left), check if the opposing side of the window (eg.
+ // right) is at least overlap pixels away. If less, offset the window by that difference.
+ int horizontalDiff = 0;
+ // If window is smaller than overlap, use it's smallest dimension instead
+ int overlapLR = Math.min(overlapPxX, bounds.width());
+ if (bounds.right < (rootTaskBounds.left + overlapLR)) {
+ horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left);
+ } else if (bounds.left > (rootTaskBounds.right - overlapLR)) {
+ horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left));
+ }
+ int verticalDiff = 0;
+ int overlapTB = Math.min(overlapPxY, bounds.width());
+ if (bounds.bottom < (rootTaskBounds.top + overlapTB)) {
+ verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top);
+ } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) {
+ verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top));
+ }
+ bounds.offset(horizontalDiff, verticalDiff);
}
private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) {
- if (mWmService.mDisableTransitionAnimation
- || !isVisible()
- || getSurfaceControl() == null
- || !isLeafTask()) {
+ if (!isLeafTask() || !canStartChangeTransition()) {
return false;
}
// Only do an animation into and out-of freeform mode for now. Other mode
@@ -2536,400 +2324,6 @@ class Task extends WindowContainer<WindowContainer> {
mTaskSupervisor.mLaunchParamsPersister.saveTask(this, display);
}
- /**
- * Adjust bounds to stay within root task bounds.
- *
- * Since bounds might be outside of root task bounds, this method tries to move the bounds in
- * a way that keep them unchanged, but be contained within the root task bounds.
- *
- * @param bounds Bounds to be adjusted.
- * @param rootTaskBounds Bounds within which the other bounds should remain.
- * @param overlapPxX The amount of px required to be visible in the X dimension.
- * @param overlapPxY The amount of px required to be visible in the Y dimension.
- */
- private static void fitWithinBounds(Rect bounds, Rect rootTaskBounds, int overlapPxX,
- int overlapPxY) {
- if (rootTaskBounds == null || rootTaskBounds.isEmpty() || rootTaskBounds.contains(bounds)) {
- return;
- }
-
- // For each side of the parent (eg. left), check if the opposing side of the window (eg.
- // right) is at least overlap pixels away. If less, offset the window by that difference.
- int horizontalDiff = 0;
- // If window is smaller than overlap, use it's smallest dimension instead
- int overlapLR = Math.min(overlapPxX, bounds.width());
- if (bounds.right < (rootTaskBounds.left + overlapLR)) {
- horizontalDiff = overlapLR - (bounds.right - rootTaskBounds.left);
- } else if (bounds.left > (rootTaskBounds.right - overlapLR)) {
- horizontalDiff = -(overlapLR - (rootTaskBounds.right - bounds.left));
- }
- int verticalDiff = 0;
- int overlapTB = Math.min(overlapPxY, bounds.width());
- if (bounds.bottom < (rootTaskBounds.top + overlapTB)) {
- verticalDiff = overlapTB - (bounds.bottom - rootTaskBounds.top);
- } else if (bounds.top > (rootTaskBounds.bottom - overlapTB)) {
- verticalDiff = -(overlapTB - (rootTaskBounds.bottom - bounds.top));
- }
- bounds.offset(horizontalDiff, verticalDiff);
- }
-
- /**
- * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
- * intersectBounds on a side, then the respective side will not be intersected.
- *
- * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
- * inset on that side is no-longer applicable. This scenario happens when a task's minimal
- * bounds are larger than the provided parent/display bounds.
- *
- * @param inOutBounds the bounds to intersect.
- * @param intersectBounds the bounds to intersect with.
- * @param intersectInsets insets to apply to intersectBounds before intersecting.
- */
- static void intersectWithInsetsIfFits(
- Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
- if (inOutBounds.right <= intersectBounds.right) {
- inOutBounds.right =
- Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
- }
- if (inOutBounds.bottom <= intersectBounds.bottom) {
- inOutBounds.bottom =
- Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
- }
- if (inOutBounds.left >= intersectBounds.left) {
- inOutBounds.left =
- Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
- }
- if (inOutBounds.top >= intersectBounds.top) {
- inOutBounds.top =
- Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
- }
- }
-
- /**
- * Gets bounds with non-decor and stable insets applied respectively.
- *
- * If bounds overhangs the display, those edges will not get insets. See
- * {@link #intersectWithInsetsIfFits}
- *
- * @param outNonDecorBounds where to place bounds with non-decor insets applied.
- * @param outStableBounds where to place bounds with stable insets applied.
- * @param bounds the bounds to inset.
- */
- private void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo) {
- outNonDecorBounds.set(bounds);
- outStableBounds.set(bounds);
- final Task rootTask = getRootTask();
- if (rootTask == null || rootTask.mDisplayContent == null) {
- return;
- }
- mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
-
- final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
- policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
- displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets);
- intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
-
- policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
- }
-
- /**
- * Forces the app bounds related configuration can be computed by
- * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
- * ActivityRecord.CompatDisplayInsets)}.
- */
- private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
- final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (appBounds != null) {
- appBounds.setEmpty();
- }
- inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
- if (overrideDisplayInfo != null) {
- // Make sure the screen related configs can be computed by the provided display info.
- inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig) {
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
- if (compatInsets != null) {
- // Make sure the app bounds can be computed by the compat insets.
- invalidateAppBoundsConfig(inOutConfig);
- }
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- compatInsets);
- }
-
- /**
- * Calculates configuration values used by the client to get resources. This should be run
- * using app-facing bounds (bounds unmodified by animations or transient interactions).
- *
- * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
- * configuring an "inherit-bounds" window which means that all configuration settings would
- * just be inherited from the parent configuration.
- **/
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
- int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- windowingMode = parentConfig.windowConfiguration.getWindowingMode();
- }
-
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = parentConfig.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
-
- // The bounds may have been overridden at this level. If the parent cannot cover these
- // bounds, the configuration is still computed according to the override bounds.
- final boolean insideParentBounds;
-
- final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
- final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
- if (resolvedBounds == null || resolvedBounds.isEmpty()) {
- mTmpFullBounds.set(parentBounds);
- insideParentBounds = true;
- } else {
- mTmpFullBounds.set(resolvedBounds);
- insideParentBounds = parentBounds.contains(resolvedBounds);
- }
-
- // Non-null compatibility insets means the activity prefers to keep its original size, so
- // out bounds doesn't need to be restricted by the parent or current display
- final boolean customContainerPolicy = compatInsets != null;
-
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- // App-bounds hasn't been overridden, so calculate a value for it.
- inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-
- if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) {
- final Rect containingAppBounds;
- if (insideParentBounds) {
- containingAppBounds = parentConfig.windowConfiguration.getAppBounds();
- } else {
- // Restrict appBounds to display non-decor rather than parent because the
- // override bounds are beyond the parent. Otherwise, it won't match the
- // overridden bounds.
- final TaskDisplayArea displayArea = getDisplayArea();
- containingAppBounds = displayArea != null
- ? displayArea.getWindowConfiguration().getAppBounds() : null;
- }
- if (containingAppBounds != null && !containingAppBounds.isEmpty()) {
- outAppBounds.intersect(containingAppBounds);
- }
- }
- }
-
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
- || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
- mTmpNonDecorBounds.set(mTmpFullBounds);
- mTmpStableBounds.set(mTmpFullBounds);
- } else if (!customContainerPolicy
- && (overrideDisplayInfo != null || getDisplayContent() != null)) {
- final DisplayInfo di = overrideDisplayInfo != null
- ? overrideDisplayInfo
- : getDisplayContent().getDisplayInfo();
-
- // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
- // area, i.e. the screen area without the system bars.
- // The non decor inset are areas that could never be removed in Honeycomb. See
- // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
- } else {
- // Apply the given non-decor and stable insets to calculate the corresponding bounds
- // for screen size of configuration.
- int rotation = inOutConfig.windowConfiguration.getRotation();
- if (rotation == ROTATION_UNDEFINED) {
- rotation = parentConfig.windowConfiguration.getRotation();
- }
- if (rotation != ROTATION_UNDEFINED && customContainerPolicy) {
- mTmpNonDecorBounds.set(mTmpFullBounds);
- mTmpStableBounds.set(mTmpFullBounds);
- compatInsets.getBoundsByRotation(mTmpBounds, rotation);
- intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
- compatInsets.mNonDecorInsets[rotation]);
- intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
- compatInsets.mStableInsets[rotation]);
- outAppBounds.set(mTmpNonDecorBounds);
- } else {
- // Set to app bounds because it excludes decor insets.
- mTmpNonDecorBounds.set(outAppBounds);
- mTmpStableBounds.set(outAppBounds);
- }
- }
-
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
- inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
- ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
- : overrideScreenWidthDp;
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
- inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
- ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
- : overrideScreenHeightDp;
- }
-
- if (inOutConfig.smallestScreenWidthDp
- == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
- if (WindowConfiguration.isFloating(windowingMode)) {
- // For floating tasks, calculate the smallest width from the bounds of the task
- inOutConfig.smallestScreenWidthDp = (int) (
- Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
- }
- // otherwise, it will just inherit
- }
- }
-
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
- if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
- // For calculating screen layout, we need to use the non-decor inset screen area for the
- // calculation for compatibility reasons, i.e. screen area without system bars that
- // could never go away in Honeycomb.
- int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
- int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
- // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
- // undefined so it can't be used.
- if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- compatScreenWidthDp = inOutConfig.screenWidthDp;
- }
- if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- compatScreenHeightDp = inOutConfig.screenHeightDp;
- }
- // Reducing the screen layout starting from its parent config.
- inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
- compatScreenWidthDp, compatScreenHeightDp);
- }
- }
-
- /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
- static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp,
- int screenHeightDp) {
- sourceScreenLayout = sourceScreenLayout
- & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
- final int longSize = Math.max(screenWidthDp, screenHeightDp);
- final int shortSize = Math.min(screenWidthDp, screenHeightDp);
- return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
- }
-
- @Override
- void resolveOverrideConfiguration(Configuration newParentConfig) {
- mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
- super.resolveOverrideConfiguration(newParentConfig);
-
- int windowingMode =
- getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
- final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
-
- // Resolve override windowing mode to fullscreen for home task (even on freeform
- // display), or split-screen if in split-screen mode.
- if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
- windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
- ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
- getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
- }
-
- // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
- // pinned windowing mode.
- if (!supportsMultiWindow()) {
- final int candidateWindowingMode =
- windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
- if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
- && candidateWindowingMode != WINDOWING_MODE_PINNED) {
- getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
- WINDOWING_MODE_FULLSCREEN);
- }
- }
-
- if (isLeafTask()) {
- resolveLeafOnlyOverrideConfigs(newParentConfig, mTmpBounds /* previousBounds */);
- }
- computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
- }
-
- private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig,
- Rect previousBounds) {
-
- int windowingMode =
- getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_UNDEFINED) {
- windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
- }
- // Commit the resolved windowing mode so the canSpecifyOrientation won't get the old
- // mode that may cause the bounds to be miscalculated, e.g. letterboxed.
- getConfiguration().windowConfiguration.setWindowingMode(windowingMode);
- Rect outOverrideBounds =
- getResolvedOverrideConfiguration().windowConfiguration.getBounds();
-
- if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
- // Use empty bounds to indicate "fill parent".
- outOverrideBounds.setEmpty();
- // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
- // the parent or display is smaller than the size, the content may be cropped.
- return;
- }
-
- adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
- if (windowingMode == WINDOWING_MODE_FREEFORM) {
- computeFreeformBounds(outOverrideBounds, newParentConfig);
- return;
- }
- }
-
- /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
- private void computeFreeformBounds(@NonNull Rect outBounds,
- @NonNull Configuration newParentConfig) {
- // by policy, make sure the window remains within parent somewhere
- final float density =
- ((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
- final Rect parentBounds =
- new Rect(newParentConfig.windowConfiguration.getBounds());
- final DisplayContent display = getDisplayContent();
- if (display != null) {
- // If a freeform window moves below system bar, there is no way to move it again
- // by touch. Because its caption is covered by system bar. So we exclude them
- // from root task bounds. and then caption will be shown inside stable area.
- final Rect stableBounds = new Rect();
- display.getStableRect(stableBounds);
- parentBounds.intersect(stableBounds);
- }
-
- fitWithinBounds(outBounds, parentBounds,
- (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
- (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
-
- // Prevent to overlap caption with stable insets.
- final int offsetTop = parentBounds.top - outBounds.top;
- if (offsetTop > 0) {
- outBounds.offset(0, offsetTop);
- }
- }
-
Rect updateOverrideConfigurationFromLaunchBounds() {
// If the task is controlled by another organized task, do not set override
// configurations and let its parent (organized task) to control it;
@@ -2978,24 +2372,11 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- int getDisplayId() {
- final DisplayContent dc = getDisplayContent();
- return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
- }
-
/** @return Id of root task. */
int getRootTaskId() {
return getRootTask().mTaskId;
}
- Task getRootTask() {
- final WindowContainer parent = getParent();
- if (parent == null) return this;
-
- final Task parentTask = parent.asTask();
- return parentTask == null ? this : parentTask.getRootTask();
- }
-
/** @return the first organized task. */
@Nullable
Task getOrganizedTask() {
@@ -3024,6 +2405,16 @@ class Task extends WindowContainer<WindowContainer> {
return true;
}
+ /** Return the top-most leaf-task under this one, or this task if it is a leaf. */
+ public Task getTopLeafTask() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final Task child = mChildren.get(i).asTask();
+ if (child == null) continue;
+ return child.getTopLeafTask();
+ }
+ return this;
+ }
+
int getDescendantTaskCount() {
final int[] currentCount = {0};
final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
@@ -3108,12 +2499,12 @@ class Task extends WindowContainer<WindowContainer> {
// and focused application if needed.
focusableTask.moveToFront(myReason);
// Top display focused root task is changed, update top resumed activity if needed.
- if (rootTask.getResumedActivity() != null) {
+ if (rootTask.getTopResumedActivity() != null) {
mTaskSupervisor.updateTopResumedActivityIfNeeded();
// Set focused app directly because if the next focused activity is already resumed
// (e.g. the next top activity is on a different display), there won't have activity
// state change to update it.
- mAtmService.setResumedActivityUncheckLocked(rootTask.getResumedActivity(), reason);
+ mAtmService.setResumedActivityUncheckLocked(rootTask.getTopResumedActivity(), reason);
}
return rootTask;
}
@@ -3162,17 +2553,16 @@ class Task extends WindowContainer<WindowContainer> {
// Figure-out min/max possible position depending on if child can show for current user.
int minPosition = (canShowChild) ? computeMinUserPosition(0, size) : 0;
- int maxPosition = (canShowChild) ? size - 1 : computeMaxUserPosition(size - 1);
- if (!hasChild(wc)) {
- // Increase the maxPosition because children size will grow once wc is added.
- ++maxPosition;
+ int maxPosition = minPosition;
+ if (size > 0) {
+ maxPosition = (canShowChild) ? size - 1 : computeMaxUserPosition(size - 1);
}
// Factor in always-on-top children in max possible position.
if (!wc.isAlwaysOnTop()) {
// We want to place all non-always-on-top containers below always-on-top ones.
while (maxPosition > minPosition) {
- if (!mChildren.get(maxPosition - 1).isAlwaysOnTop()) break;
+ if (!mChildren.get(maxPosition).isAlwaysOnTop()) break;
--maxPosition;
}
}
@@ -3183,6 +2573,12 @@ class Task extends WindowContainer<WindowContainer> {
} else if (suggestedPosition == POSITION_TOP && maxPosition >= (size - 1)) {
return POSITION_TOP;
}
+
+ // Increase the maxPosition because children size will grow once wc is added.
+ if (!hasChild(wc)) {
+ ++maxPosition;
+ }
+
// Reset position based on minimum/maximum possible positions.
return Math.min(Math.max(suggestedPosition, minPosition), maxPosition);
}
@@ -3203,25 +2599,12 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- @VisibleForTesting
- boolean hasWindowsAlive() {
- return getActivity(ActivityRecord::hasWindowsAlive) != null;
- }
-
- @VisibleForTesting
- boolean shouldDeferRemoval() {
- if (mChildren.isEmpty()) {
- // No reason to defer removal of a Task that doesn't have any child.
- return false;
- }
- return hasWindowsAlive() && getRootTask().isAnimating(TRANSITION | CHILDREN);
- }
-
@Override
void removeImmediately() {
removeImmediately("removeTask");
}
+ @Override
void removeImmediately(String reason) {
if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask:" + reason + " removing taskId=" + mTaskId);
if (mRemoving) {
@@ -3355,10 +2738,14 @@ class Task extends WindowContainer<WindowContainer> {
}
boolean isResizeable() {
+ return isResizeable(/* checkPictureInPictureSupport */ true);
+ }
+
+ boolean isResizeable(boolean checkPictureInPictureSupport) {
final boolean forceResizable = mAtmService.mForceResizableActivities
&& getActivityType() == ACTIVITY_TYPE_STANDARD;
return forceResizable || ActivityInfo.isResizeableMode(mResizeMode)
- || mSupportsPictureInPicture;
+ || (mSupportsPictureInPicture && checkPictureInPictureSupport);
}
/**
@@ -3477,6 +2864,30 @@ class Task extends WindowContainer<WindowContainer> {
return;
}
+ /**
+ * Account for specified insets to crop the animation bounds by to avoid the animation
+ * occurring over "out of bounds" regions
+ *
+ * For example this is used to make sure the tasks are cropped to be fully above the
+ * taskbar when animating.
+ *
+ * @param animationBounds The animations bounds to adjust to account for the custom spec insets.
+ */
+ void adjustAnimationBoundsForTransition(Rect animationBounds) {
+ TaskTransitionSpec spec = mWmService.mTaskTransitionSpec;
+ if (spec != null) {
+ for (@InsetsState.InternalInsetsType int insetType : spec.animationBoundInsets) {
+ InsetsSourceProvider insetProvider = getDisplayContent()
+ .getInsetsStateController()
+ .getSourceProvider(insetType);
+
+ Insets insets = insetProvider.getSource().calculateVisibleInsets(
+ animationBounds);
+ animationBounds.inset(insets);
+ }
+ }
+ }
+
void setDragResizing(boolean dragResizing, int dragResizeMode) {
if (mDragResizing != dragResizing) {
// No need to check if the mode is allowed if it's leaving dragResize
@@ -3564,18 +2975,6 @@ class Task extends WindowContainer<WindowContainer> {
mForceShowForAllUsers = forceShowForAllUsers;
}
- @Override
- public boolean isAttached() {
- final TaskDisplayArea taskDisplayArea = getDisplayArea();
- return taskDisplayArea != null && !taskDisplayArea.isRemoved();
- }
-
- @Override
- @Nullable
- TaskDisplayArea getDisplayArea() {
- return (TaskDisplayArea) super.getDisplayArea();
- }
-
/**
* When we are in a floating root task (Freeform, Pinned, ...) we calculate
* insets differently. However if we are animating to the fullscreen root task
@@ -3586,70 +2985,55 @@ class Task extends WindowContainer<WindowContainer> {
return getWindowConfiguration().tasksAreFloating() && !mPreserveNonFloatingState;
}
- /**
- * Returns true if the root task is translucent and can have other contents visible behind it if
- * needed. A root task is considered translucent if it don't contain a visible or
- * starting (about to be visible) activity that is fullscreen (opaque).
- * @param starting The currently starting activity or null if there is none.
- */
- @VisibleForTesting
- boolean isTranslucent(ActivityRecord starting) {
- if (!isAttached() || isForceHidden()) {
- return true;
- }
- final PooledPredicate p = PooledLambda.obtainPredicate(Task::isOpaqueActivity,
- PooledLambda.__(ActivityRecord.class), starting);
- final ActivityRecord opaque = getActivity(p);
- p.recycle();
- return opaque == null;
- }
-
- private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
- if (r.finishing) {
- // We don't factor in finishing activities when determining translucency since
- // they will be gone soon.
- return false;
- }
-
- if (!r.visibleIgnoringKeyguard && r != starting) {
- // Also ignore invisible activities that are not the currently starting
- // activity (about to be visible).
- return false;
- }
-
- if (r.occludesParent()) {
- // Root task isn't translucent if it has at least one fullscreen activity
- // that is visible.
- return true;
- }
- return false;
- }
-
/** Returns the top-most activity that occludes the given one, or {@code null} if none. */
@Nullable
ActivityRecord getOccludingActivityAbove(ActivityRecord activity) {
- final ActivityRecord top = getActivity(ActivityRecord::occludesParent,
- true /* traverseTopToBottom */, activity);
+ final ActivityRecord top = getActivity(r -> {
+ if (r == activity) {
+ // Reached the given activity, return the activity to stop searching.
+ return true;
+ }
+
+ if (!r.occludesParent()) {
+ return false;
+ }
+
+ TaskFragment parent = r.getTaskFragment();
+ if (parent == activity.getTaskFragment()) {
+ // Found it. This activity on top of the given activity on the same TaskFragment.
+ return true;
+ }
+ if (isSelfOrNonEmbeddedTask(parent.asTask())) {
+ // Found it. This activity is the direct child of a leaf Task without being
+ // embedded.
+ return true;
+ }
+ // The candidate activity is being embedded. Checking if the bounds of the containing
+ // TaskFragment equals to the outer TaskFragment.
+ TaskFragment grandParent = parent.getParent().asTaskFragment();
+ while (grandParent != null) {
+ if (!parent.getBounds().equals(grandParent.getBounds())) {
+ // Not occluding the grandparent.
+ break;
+ }
+ if (isSelfOrNonEmbeddedTask(grandParent.asTask())) {
+ // Found it. The activity occludes its parent TaskFragment and the parent
+ // TaskFragment also occludes its parent all the way up.
+ return true;
+ }
+ parent = grandParent;
+ grandParent = parent.getParent().asTaskFragment();
+ }
+ return false;
+ });
return top != activity ? top : null;
}
- /** Iterates through all occluded activities. */
- void forAllOccludedActivities(Consumer<ActivityRecord> handleOccludedActivity) {
- if (!shouldBeVisible(null /* starting */)) {
- // The root task is invisible so all activities are occluded.
- forAllActivities(handleOccludedActivity);
- return;
- }
- final ActivityRecord topOccluding = getOccludingActivityAbove(null);
- if (topOccluding == null) {
- // No activities are occluded.
- return;
+ private boolean isSelfOrNonEmbeddedTask(Task task) {
+ if (task == this) {
+ return true;
}
- // Invoke the callback on the activities behind the top occluding activity.
- forAllActivities(r -> {
- handleOccludedActivity.accept(r);
- return false;
- }, topOccluding, false /* includeBoundary */, true /* traverseTopToBottom */);
+ return task != null && !task.isEmbedded();
}
@Override
@@ -3657,21 +3041,6 @@ class Task extends WindowContainer<WindowContainer> {
return super.makeAnimationLeash().setMetadata(METADATA_TASK_ID, mTaskId);
}
- @Override
- void resetSurfacePositionForAnimationLeash(SurfaceControl.Transaction t) {
- super.resetSurfacePositionForAnimationLeash(t);
- }
-
- @Override
- Rect getAnimationBounds(int appRootTaskClipMode) {
- // TODO(b/131661052): we should remove appRootTaskClipMode with hierarchical animations.
- if (appRootTaskClipMode == ROOT_TASK_CLIP_BEFORE_ANIM && getRootTask() != null) {
- // Using the root task bounds here effectively applies the clipping before animation.
- return getRootTask().getBounds();
- }
- return super.getAnimationBounds(appRootTaskClipMode);
- }
-
boolean shouldAnimate() {
/**
* Animations are handled by the TaskOrganizer implementation.
@@ -3701,36 +3070,11 @@ class Task extends WindowContainer<WindowContainer> {
return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS);
}
- @Override
- RemoteAnimationTarget createRemoteAnimationTarget(
- RemoteAnimationController.RemoteAnimationRecord record) {
- final ActivityRecord activity = getTopMostActivity();
- return activity != null ? activity.createRemoteAnimationTarget(record) : null;
- }
-
- @Override
- boolean canCreateRemoteAnimationTarget() {
- return true;
- }
-
WindowState getTopVisibleAppMainWindow() {
final ActivityRecord activity = getTopVisibleActivity();
return activity != null ? activity.findMainWindow() : null;
}
- ActivityRecord topRunningActivity() {
- return topRunningActivity(false /* focusableOnly */);
- }
-
- ActivityRecord topRunningActivity(boolean focusableOnly) {
- // Split into 2 to avoid object creation due to variable capture.
- if (focusableOnly) {
- return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
- } else {
- return getActivity(ActivityRecord::canBeTopRunning);
- }
- }
-
ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
final PooledPredicate p = PooledLambda.obtainPredicate(Task::isTopRunningNonDelayed
, PooledLambda.__(ActivityRecord.class), notTop);
@@ -3785,16 +3129,6 @@ class Task extends WindowContainer<WindowContainer> {
});
}
- boolean isTopActivityFocusable() {
- final ActivityRecord r = topRunningActivity();
- return r != null ? r.isFocusable()
- : (isFocusable() && getWindowConfiguration().canReceiveKeys());
- }
-
- boolean isFocusableAndVisible() {
- return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
- }
-
void positionChildAtTop(ActivityRecord child) {
positionChildAt(child, POSITION_TOP);
}
@@ -3838,14 +3172,6 @@ class Task extends WindowContainer<WindowContainer> {
}
@Override
- boolean fillsParent() {
- // From the perspective of policy, we still want to report that this task fills parent
- // in fullscreen windowing mode even it doesn't match parent bounds because there will be
- // letterbox around its real content.
- return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
- }
-
- @Override
void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
boolean isLeafTask = true;
@@ -3906,6 +3232,41 @@ class Task extends WindowContainer<WindowContainer> {
return false;
}
+ /** Iterates through all leaf task fragments and the leaf tasks. */
+ void forAllLeafTasksAndLeafTaskFragments(final Consumer<TaskFragment> callback,
+ boolean traverseTopToBottom) {
+ forAllLeafTasks(task -> {
+ if (task.isLeafTaskFragment()) {
+ callback.accept(task);
+ return;
+ }
+
+ // A leaf task that may contains both activities and task fragments.
+ boolean consumed = false;
+ if (traverseTopToBottom) {
+ for (int i = task.mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = task.mChildren.get(i);
+ if (child.asTaskFragment() != null) {
+ child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+ } else if (child.asActivityRecord() != null && !consumed) {
+ callback.accept(task);
+ consumed = true;
+ }
+ }
+ } else {
+ for (int i = 0; i < task.mChildren.size(); i++) {
+ final WindowContainer child = task.mChildren.get(i);
+ if (child.asTaskFragment() != null) {
+ child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+ } else if (child.asActivityRecord() != null && !consumed) {
+ callback.accept(task);
+ consumed = true;
+ }
+ }
+ }
+ }, traverseTopToBottom);
+ }
+
@Override
boolean forAllRootTasks(Function<Task, Boolean> callback, boolean traverseTopToBottom) {
return isRootTask() ? callback.apply(this) : false;
@@ -4025,19 +3386,9 @@ class Task extends WindowContainer<WindowContainer> {
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
super.dump(pw, prefix, dumpAll);
- pw.println(prefix + "bounds=" + getBounds().toShortString());
- final String doublePrefix = prefix + " ";
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- final WindowContainer<?> child = mChildren.get(i);
- pw.println(prefix + "* " + child);
- // Only dump non-activity because full activity info is already printed by
- // RootWindowContainer#dumpActivities.
- if (child.asActivityRecord() == null) {
- child.dump(pw, doublePrefix, dumpAll);
- }
- }
if (!mExitingActivities.isEmpty()) {
+ final String doublePrefix = prefix + " ";
pw.println();
pw.println(prefix + "Exiting application tokens:");
for (int i = mExitingActivities.size() - 1; i >= 0; i--) {
@@ -4076,6 +3427,9 @@ class Task extends WindowContainer<WindowContainer> {
info.userId = isLeafTask() ? mUserId : mCurrentUser;
info.taskId = mTaskId;
info.displayId = getDisplayId();
+ if (tda != null) {
+ info.displayAreaFeatureId = tda.mFeatureId;
+ }
info.isRunning = getTopNonFinishingActivity() != null;
final Intent baseIntent = getBaseIntent();
// Make a copy of base intent because this is like a snapshot info.
@@ -4135,6 +3489,7 @@ class Task extends WindowContainer<WindowContainer> {
: INVALID_TASK_ID;
info.isFocused = isFocused();
info.isVisible = hasVisibleChildren();
+ info.isSleeping = shouldSleepActivities();
ActivityRecord topRecord = getTopNonFinishingActivity();
info.mTopActivityLocusId = topRecord != null ? topRecord.getLocusId() : null;
}
@@ -4145,9 +3500,9 @@ class Task extends WindowContainer<WindowContainer> {
private @Nullable PictureInPictureParams getPictureInPictureParams(Task top) {
if (top == null) return null;
- final ActivityRecord topVisibleActivity = top.getTopVisibleActivity();
- return (topVisibleActivity == null || topVisibleActivity.pictureInPictureArgs.empty())
- ? null : new PictureInPictureParams(topVisibleActivity.pictureInPictureArgs);
+ final ActivityRecord topMostActivity = top.getTopMostActivity();
+ return (topMostActivity == null || topMostActivity.pictureInPictureArgs.empty())
+ ? null : new PictureInPictureParams(topMostActivity.pictureInPictureArgs);
}
Rect getDisplayCutoutInsets() {
@@ -4187,6 +3542,7 @@ class Task extends WindowContainer<WindowContainer> {
final WindowState mainWindow = activity.findMainWindow();
if (mainWindow != null) {
info.mainWindowLayoutParams = mainWindow.getAttrs();
+ info.requestedVisibilities.set(mainWindow.getRequestedVisibilities());
}
// If the developer has persist a different configuration, we need to override it to the
// starting window because persisted configuration does not effect to Task.
@@ -4214,184 +3570,6 @@ class Task extends WindowContainer<WindowContainer> {
return this;
}
- /**
- * Returns true if the task should be visible.
- *
- * @param starting The currently starting activity or null if there is none.
- */
- boolean shouldBeVisible(ActivityRecord starting) {
- return getVisibility(starting) != TASK_VISIBILITY_INVISIBLE;
- }
-
- /**
- * Returns true if the task should be visible.
- *
- * @param starting The currently starting activity or null if there is none.
- */
- @TaskVisibility
- int getVisibility(ActivityRecord starting) {
- if (!isAttached() || isForceHidden()) {
- return TASK_VISIBILITY_INVISIBLE;
- }
-
- if (isTopActivityLaunchedBehind()) {
- return TASK_VISIBILITY_VISIBLE;
- }
-
- boolean gotRootSplitScreenTask = false;
- boolean gotOpaqueSplitScreenPrimary = false;
- boolean gotOpaqueSplitScreenSecondary = false;
- boolean gotTranslucentFullscreen = false;
- boolean gotTranslucentSplitScreenPrimary = false;
- boolean gotTranslucentSplitScreenSecondary = false;
- boolean shouldBeVisible = true;
-
- // This root task is only considered visible if all its parent root tasks are considered
- // visible, so check the visibility of all ancestor root task first.
- final WindowContainer parent = getParent();
- if (parent.asTask() != null) {
- final int parentVisibility = parent.asTask().getVisibility(starting);
- if (parentVisibility == TASK_VISIBILITY_INVISIBLE) {
- // Can't be visible if parent isn't visible
- return TASK_VISIBILITY_INVISIBLE;
- } else if (parentVisibility == TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
- // Parent is behind a translucent container so the highest visibility this container
- // can get is that.
- gotTranslucentFullscreen = true;
- }
- }
-
- final List<Task> adjacentTasks = new ArrayList<>();
- final int windowingMode = getWindowingMode();
- final boolean isAssistantType = isActivityTypeAssistant();
- for (int i = parent.getChildCount() - 1; i >= 0; --i) {
- final WindowContainer wc = parent.getChildAt(i);
- final Task other = wc.asTask();
- if (other == null) continue;
-
- final boolean hasRunningActivities = other.topRunningActivity() != null;
- if (other == this) {
- // Should be visible if there is no other stack occluding it, unless it doesn't
- // have any running activities, not starting one and not home stack.
- shouldBeVisible = hasRunningActivities || isInTask(starting) != null
- || isActivityTypeHome();
- break;
- }
-
- if (!hasRunningActivities) {
- continue;
- }
-
- final int otherWindowingMode = other.getWindowingMode();
-
- if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- if (other.isTranslucent(starting)) {
- // Can be visible behind a translucent fullscreen stack.
- gotTranslucentFullscreen = true;
- continue;
- }
- return TASK_VISIBILITY_INVISIBLE;
- } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
- && other.matchParentBounds()) {
- if (other.isTranslucent(starting)) {
- // Can be visible behind a translucent task.
- gotTranslucentFullscreen = true;
- continue;
- }
- // Multi-window task that matches parent bounds would occlude other children.
- return TASK_VISIBILITY_INVISIBLE;
- } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- && !gotOpaqueSplitScreenPrimary) {
- gotRootSplitScreenTask = true;
- gotTranslucentSplitScreenPrimary = other.isTranslucent(starting);
- gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- && gotOpaqueSplitScreenPrimary) {
- // Can not be visible behind another opaque stack in split-screen-primary mode.
- return TASK_VISIBILITY_INVISIBLE;
- }
- } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- && !gotOpaqueSplitScreenSecondary) {
- gotRootSplitScreenTask = true;
- gotTranslucentSplitScreenSecondary = other.isTranslucent(starting);
- gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
- if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- && gotOpaqueSplitScreenSecondary) {
- // Can not be visible behind another opaque stack in split-screen-secondary mode.
- return TASK_VISIBILITY_INVISIBLE;
- }
- }
- if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
- // Can not be visible if we are in split-screen windowing mode and both halves of
- // the screen are opaque.
- return TASK_VISIBILITY_INVISIBLE;
- }
- if (isAssistantType && gotRootSplitScreenTask) {
- // Assistant stack can't be visible behind split-screen. In addition to this not
- // making sense, it also works around an issue here we boost the z-order of the
- // assistant window surfaces in window manager whenever it is visible.
- return TASK_VISIBILITY_INVISIBLE;
- }
- if (other.mAdjacentTask != null) {
- if (adjacentTasks.contains(other.mAdjacentTask)) {
- if (other.isTranslucent(starting)
- || other.mAdjacentTask.isTranslucent(starting)) {
- // Can be visible behind a translucent adjacent tasks.
- gotTranslucentFullscreen = true;
- continue;
- }
- // Can not be visible behind adjacent tasks.
- return TASK_VISIBILITY_INVISIBLE;
- } else {
- adjacentTasks.add(other);
- }
- }
- }
-
- if (!shouldBeVisible) {
- return TASK_VISIBILITY_INVISIBLE;
- }
-
- // Handle cases when there can be a translucent split-screen stack on top.
- switch (windowingMode) {
- case WINDOWING_MODE_FULLSCREEN:
- if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
- // At least one of the split-screen stacks that covers this one is translucent.
- // When in split mode, home task will be reparented to the secondary split while
- // leaving tasks not supporting split below. Due to
- // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to
- // the bottom, this makes sure tasks not in split roots won't occlude home task
- // unexpectedly.
- return TASK_VISIBILITY_INVISIBLE;
- }
- break;
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
- if (gotTranslucentSplitScreenPrimary) {
- // Covered by translucent primary split-screen on top.
- return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
- }
- break;
- case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
- if (gotTranslucentSplitScreenSecondary) {
- // Covered by translucent secondary split-screen on top.
- return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
- }
- break;
- }
-
- // Lastly - check if there is a translucent fullscreen stack on top.
- return gotTranslucentFullscreen ? TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
- : TASK_VISIBILITY_VISIBLE;
- }
-
- private boolean isTopActivityLaunchedBehind() {
- final ActivityRecord top = topRunningActivity();
- if (top != null && top.mLaunchTaskBehind) {
- return true;
- }
- return false;
- }
-
ActivityRecord isInTask(ActivityRecord r) {
if (r == null) {
return null;
@@ -4502,9 +3680,6 @@ class Task extends WindowContainer<WindowContainer> {
pw.print(" isResizeable="); pw.println(isResizeable());
pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
- if (mForceNotOrganized) {
- pw.print(prefix); pw.println("mForceNotOrganized=true");
- }
}
@Override
@@ -4521,6 +3696,8 @@ class Task extends WindowContainer<WindowContainer> {
}
sb.append(" visible=");
sb.append(shouldBeVisible(null /* starting */));
+ sb.append(" visibleRequested=");
+ sb.append(isVisibleRequested());
sb.append(" mode=");
sb.append(windowingModeToString(getWindowingMode()));
sb.append(" translucent=");
@@ -4574,7 +3751,7 @@ class Task extends WindowContainer<WindowContainer> {
// Increment the total number of non-finishing activities
numActivities++;
- if (top == null || (top.isState(ActivityState.INITIALIZING))) {
+ if (top == null || (top.isState(INITIALIZING))) {
top = r;
// Reset the number of running activities until we hit the first non-initializing
// activity
@@ -4978,10 +4155,6 @@ class Task extends WindowContainer<WindowContainer> {
}
private boolean canBeOrganized() {
- if (mForceNotOrganized || !mAtmService.mTaskOrganizerController
- .isSupportedWindowingMode(getWindowingMode())) {
- return false;
- }
// All root tasks can be organized
if (isRootTask()) {
return true;
@@ -5134,9 +4307,8 @@ class Task extends WindowContainer<WindowContainer> {
return setTaskOrganizer(null);
}
- final int windowingMode = getWindowingMode();
final TaskOrganizerController controller = mWmService.mAtmService.mTaskOrganizerController;
- final ITaskOrganizer organizer = controller.getTaskOrganizer(windowingMode);
+ final ITaskOrganizer organizer = controller.getTaskOrganizer();
if (!forceUpdate && mTaskOrganizer == organizer) {
return false;
}
@@ -5154,11 +4326,11 @@ class Task extends WindowContainer<WindowContainer> {
/**
* @return true if the task is currently focused.
*/
- boolean isFocused() {
- if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) {
+ private boolean isFocused() {
+ if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
return false;
}
- return mDisplayContent.mCurrentFocus.getTask() == this;
+ return mDisplayContent.mFocusedApp.getTask() == this;
}
/**
@@ -5212,13 +4384,12 @@ class Task extends WindowContainer<WindowContainer> {
}
/**
- * Called on the task of a window which gained or lost focus.
+ * Called on the task when it gained or lost focus.
* @param hasFocus
*/
- void onWindowFocusChanged(boolean hasFocus) {
+ void onAppFocusChanged(boolean hasFocus) {
updateShadowsRadius(hasFocus, getSyncTransaction());
- // TODO(b/180525887): Un-comment once there is resolution on the bug.
- // dispatchTaskInfoChangedIfNeeded(false /* force */);
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void onPictureInPictureParamsChanged() {
@@ -5317,9 +4488,7 @@ class Task extends WindowContainer<WindowContainer> {
return super.isAlwaysOnTop();
}
- /**
- * Returns whether this task is currently forced to be hidden for any reason.
- */
+ @Override
protected boolean isForceHidden() {
return mForceHiddenFlags != 0;
}
@@ -5435,14 +4604,15 @@ class Task extends WindowContainer<WindowContainer> {
}
super.setWindowingMode(windowingMode);
- // Try reparent pinned activity back to its original task after onConfigurationChanged
- // cascade finishes. This is done on Task level instead of
- // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
- // we set final windowing mode on the ActivityRecord first and then on its Task when
- // the exit PiP transition finishes. Meanwhile, the exit transition is always
- // performed on its original task, reparent immediately in ActivityRecord breaks it.
- if (currentMode == WINDOWING_MODE_PINNED) {
- if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+ if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) {
+ // Try reparent pinned activity back to its original task after
+ // onConfigurationChanged cascade finishes. This is done on Task level instead of
+ // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit
+ // PiP, we set final windowing mode on the ActivityRecord first and then on its
+ // Task when the exit PiP transition finishes. Meanwhile, the exit transition is
+ // always performed on its original task, reparent immediately in ActivityRecord
+ // breaks it.
+ if (topActivity.getLastParentBeforePip() != null) {
// Do not reparent if the pinned task is in removal, indicated by the
// force hidden flag.
if (!isForceHidden()) {
@@ -5455,6 +4625,11 @@ class Task extends WindowContainer<WindowContainer> {
}
}
}
+ // Resume app-switches-allowed flag when exiting from pinned mode since
+ // it does not follow the ActivityStarter path.
+ if (topActivity.shouldBeVisible()) {
+ mAtmService.resumeAppSwitches();
+ }
}
if (creating) {
@@ -5464,7 +4639,8 @@ class Task extends WindowContainer<WindowContainer> {
// From fullscreen to PiP.
if (topActivity != null && currentMode == WINDOWING_MODE_FULLSCREEN
- && windowingMode == WINDOWING_MODE_PINNED) {
+ && windowingMode == WINDOWING_MODE_PINNED
+ && !mTransitionController.isShellTransitionsEnabled()) {
mDisplayContent.mPinnedTaskController
.deferOrientationChangeForEnteringPipFromFullScreenIfNeeded();
}
@@ -5472,8 +4648,10 @@ class Task extends WindowContainer<WindowContainer> {
mAtmService.continueWindowLayout();
}
- mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
- mRootWindowContainer.resumeFocusedTasksTopActivities();
+ if (!mTaskSupervisor.isRootVisibilityUpdateDeferred()) {
+ mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
}
void resumeNextFocusAfterReparent() {
@@ -5498,23 +4676,14 @@ class Task extends WindowContainer<WindowContainer> {
moveToFront(reason, null);
}
- /**
- * @param reason The reason for moving the root task to the front.
- * @param task If non-null, the task will be moved to the top of the root task.
- */
void moveToFront(String reason, Task task) {
- if (!isAttached()) {
- return;
- }
-
- final TaskDisplayArea taskDisplayArea = getDisplayArea();
-
if (inSplitScreenSecondaryWindowingMode()) {
// If the root task is in split-screen secondary mode, we need to make sure we move the
// primary split-screen root task forward in the case it is currently behind a
// fullscreen root task so both halves of the split-screen appear on-top and the
// fullscreen root task isn't cutting between them.
// TODO(b/70677280): This is a workaround until we can fix as part of b/70677280.
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
final Task topFullScreenRootTask =
taskDisplayArea.getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
if (topFullScreenRootTask != null) {
@@ -5522,10 +4691,30 @@ class Task extends WindowContainer<WindowContainer> {
taskDisplayArea.getRootSplitScreenPrimaryTask();
if (primarySplitScreenRootTask != null
&& topFullScreenRootTask.compareTo(primarySplitScreenRootTask) > 0) {
- primarySplitScreenRootTask.moveToFront(reason + " splitScreenToTop");
+ primarySplitScreenRootTask.moveToFrontInner(reason + " splitScreenToTop",
+ null /* task */);
}
}
+ } else if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) {
+ final Task adjacentTask = getAdjacentTaskFragment().asTask();
+ if (adjacentTask != null) {
+ adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */);
+ }
}
+ moveToFrontInner(reason, task);
+ }
+
+ /**
+ * @param reason The reason for moving the root task to the front.
+ * @param task If non-null, the task will be moved to the top of the root task.
+ */
+ @VisibleForTesting
+ void moveToFrontInner(String reason, Task task) {
+ if (!isAttached()) {
+ return;
+ }
+
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (!isActivityTypeHome() && returnsToHomeRootTask()) {
// Make sure the root home task is behind this root task since that is where we
@@ -5605,19 +4794,6 @@ class Task extends WindowContainer<WindowContainer> {
r.completeResumeLocked();
}
- void awakeFromSleepingLocked() {
- if (!isLeafTask()) {
- forAllLeafTasks((task) -> task.awakeFromSleepingLocked(),
- true /* traverseTopToBottom */);
- return;
- }
-
- if (mPausingActivity != null) {
- Slog.d(TAG, "awakeFromSleepingLocked: previously pausing activity didn't pause");
- mPausingActivity.activityPaused(true);
- }
- }
-
void checkReadyForSleep() {
if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5636,316 +4812,13 @@ class Task extends WindowContainer<WindowContainer> {
* the process of going to sleep (checkReadyForSleep will be called when that process finishes).
*/
boolean goToSleepIfPossible(boolean shuttingDown) {
- if (!isLeafTask()) {
- final int[] sleepInProgress = {0};
- forAllLeafTasks((t) -> {
- if (!t.goToSleepIfPossible(shuttingDown)) {
- sleepInProgress[0]++;
- }
- }, true);
- return sleepInProgress[0] == 0;
- }
-
- boolean shouldSleep = true;
- if (mResumedActivity != null) {
- // Still have something resumed; can't sleep until it is paused.
- ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
- "Sleep => pause with userLeaving=false");
-
- startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
- shouldSleep = false ;
- } else if (mPausingActivity != null) {
- // Still waiting for something to pause; can't sleep yet.
- ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
- shouldSleep = false;
- }
-
- if (!shuttingDown) {
- if (containsActivityFromRootTask(mTaskSupervisor.mStoppingActivities)) {
- // Still need to tell some activities to stop; can't sleep yet.
- ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
- mTaskSupervisor.mStoppingActivities.size());
-
- mTaskSupervisor.scheduleIdle();
- shouldSleep = false;
- }
- }
-
- if (shouldSleep) {
- ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- !PRESERVE_WINDOWS);
- }
-
- return shouldSleep;
- }
-
- private boolean containsActivityFromRootTask(List<ActivityRecord> rs) {
- for (ActivityRecord r : rs) {
- if (r.getRootTask() == this) {
- return true;
- }
- }
- return false;
- }
-
- final boolean startPausingLocked(boolean uiSleeping, ActivityRecord resuming, String reason) {
- return startPausingLocked(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
- }
-
- /**
- * Start pausing the currently resumed activity. It is an error to call this if there
- * is already an activity being paused or there is no resumed activity.
- *
- * @param userLeaving True if this should result in an onUserLeaving to the current activity.
- * @param uiSleeping True if this is happening with the user interface going to sleep (the
- * screen turning off).
- * @param resuming The activity we are currently trying to resume or null if this is not being
- * called as part of resuming the top activity, so we shouldn't try to instigate
- * a resume here if not null.
- * @param reason The reason of pausing the activity.
- * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
- * it to tell us when it is done.
- */
- final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
- ActivityRecord resuming, String reason) {
- if (!isLeafTask()) {
- final int[] pausing = {0};
- forAllLeafTasks((t) -> {
- if (t.startPausingLocked(userLeaving, uiSleeping, resuming, reason)) {
- pausing[0]++;
- }
- }, true /* traverseTopToBottom */);
- return pausing[0] > 0;
- }
-
- if (mPausingActivity != null) {
- Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
- + " state=" + mPausingActivity.getState());
- if (!shouldSleepActivities()) {
- // Avoid recursion among check for sleep and complete pause during sleeping.
- // Because activity will be paused immediately after resume, just let pause
- // be completed by the order of activity paused from clients.
- completePauseLocked(false, resuming);
- }
- }
- ActivityRecord prev = mResumedActivity;
-
- if (prev == null) {
- if (resuming == null) {
- Slog.wtf(TAG, "Trying to pause when nothing is resumed");
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- return false;
- }
-
- if (prev == resuming) {
- Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
- return false;
- }
-
- //Trigger Activity Pause
- if (mActivityTrigger != null) {
- mActivityTrigger.activityPauseTrigger(prev.intent, prev.info,
- prev.info.applicationInfo);
- }
-
- if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) {
- mActivityPluginDelegate.activitySuspendNotification
- (prev.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN, true);
- }
- ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
- mPausingActivity = prev;
- mLastPausedActivity = prev;
- if (prev.isNoHistory() && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
- mTaskSupervisor.mNoHistoryActivities.add(prev);
- }
- prev.setState(PAUSING, "startPausingLocked");
- prev.getTask().touchActiveTime();
-
- mAtmService.updateCpuStats();
-
- boolean pauseImmediately = false;
- boolean shouldAutoPip = false;
- if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) {
- // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
- // activity to be paused, while at the same time resuming the new resume activity
- // only if the previous activity can't go into Pip since we want to give Pip
- // activities a chance to enter Pip before resuming the next activity.
- final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState(
- "shouldResumeWhilePausing", userLeaving);
- if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
- shouldAutoPip = true;
- } else if (!lastResumedCanPip) {
- pauseImmediately = true;
- } else {
- // The previous activity may still enter PIP even though it did not allow auto-PIP.
- }
- }
-
- boolean didAutoPip = false;
- if (prev.attachedToProcess()) {
- if (shouldAutoPip) {
- ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
- + "directly: %s", prev);
-
- didAutoPip = mAtmService.enterPictureInPictureMode(prev, prev.pictureInPictureArgs);
- } else {
- schedulePauseActivity(prev, userLeaving, pauseImmediately, reason);
- }
- } else {
- mPausingActivity = null;
- mLastPausedActivity = null;
- mTaskSupervisor.mNoHistoryActivities.remove(prev);
- }
-
- // If we are not going to sleep, we want to ensure the device is
- // awake until the next activity is started.
- if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) {
- mTaskSupervisor.acquireLaunchWakelock();
- }
-
- // If already entered PIP mode, no need to keep pausing.
- if (mPausingActivity != null) {
- // Have the window manager pause its key dispatching until the new
- // activity has started. If we're pausing the activity just because
- // the screen is being turned off and the UI is sleeping, don't interrupt
- // key dispatch; the same activity will pick it up again on wakeup.
- if (!uiSleeping) {
- prev.pauseKeyDispatchingLocked();
- } else {
- ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
- }
-
- if (pauseImmediately) {
- // If the caller said they don't want to wait for the pause, then complete
- // the pause now.
- completePauseLocked(false, resuming);
- return false;
-
- } else {
- prev.schedulePauseTimeout();
- return true;
- }
-
- } else {
- // This activity either failed to schedule the pause or it entered PIP mode,
- // so just treat it as being paused now.
- ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
- if (resuming == null) {
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- return false;
- }
- }
-
- void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
- boolean pauseImmediately, String reason) {
- ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
- try {
- EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
- prev.shortComponentName, "userLeaving=" + userLeaving, reason);
-
- mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
- prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
- prev.configChangeFlags, pauseImmediately));
- } catch (Exception e) {
- // Ignore exception, if process died other code will cleanup.
- Slog.w(TAG, "Exception thrown during pause", e);
- mPausingActivity = null;
- mLastPausedActivity = null;
- mTaskSupervisor.mNoHistoryActivities.remove(prev);
- }
- }
-
- @VisibleForTesting
- void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
- // Complete the pausing process of a pausing activity, so it doesn't make sense to
- // operate on non-leaf tasks.
- warnForNonLeafTask("completePauseLocked");
-
- ActivityRecord prev = mPausingActivity;
- ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);
-
- if (prev != null) {
- prev.setWillCloseOrEnterPip(false);
- final boolean wasStopping = prev.isState(STOPPING);
- prev.setState(PAUSED, "completePausedLocked");
- if (prev.finishing) {
- // We will update the activity visibility later, no need to do in
- // completeFinishing(). Updating visibility here might also making the next
- // activities to be resumed, and could result in wrong app transition due to
- // lack of previous activity information.
- ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
- prev = prev.completeFinishing(false /* updateVisibility */,
- "completePausedLocked");
- } else if (prev.hasProcess()) {
- ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
- + "wasStopping=%b visibleRequested=%b", prev, wasStopping,
- prev.mVisibleRequested);
- if (prev.deferRelaunchUntilPaused) {
- // Complete the deferred relaunch that was waiting for pause to complete.
- ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
- prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
- } else if (wasStopping) {
- // We are also stopping, the stop request must have gone soon after the pause.
- // We can't clobber it, because the stop confirmation will not be handled.
- // We don't need to schedule another stop, we only need to let it happen.
- prev.setState(STOPPING, "completePausedLocked");
- } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
- // Clear out any deferred client hide we might currently have.
- prev.setDeferHidingClient(false);
- // If we were visible then resumeTopActivities will release resources before
- // stopping.
- prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
- "completePauseLocked");
- }
- } else {
- ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
- prev = null;
- }
- // It is possible the activity was freezing the screen before it was paused.
- // In that case go ahead and remove the freeze this activity has on the screen
- // since it is no longer visible.
- if (prev != null) {
- prev.stopFreezingScreenLocked(true /*force*/);
- }
- mPausingActivity = null;
- }
-
- if (resumeNext) {
- final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
- if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
- mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev, null);
- } else {
- checkReadyForSleep();
- final ActivityRecord top =
- topRootTask != null ? topRootTask.topRunningActivity() : null;
- if (top == null || (prev != null && top != prev)) {
- // If there are no more activities available to run, do resume anyway to start
- // something. Also if the top activity on the root task is not the just paused
- // activity, we need to go ahead and resume it to ensure we complete an
- // in-flight app switch.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
+ final int[] sleepInProgress = {0};
+ forAllLeafTasksAndLeafTaskFragments(taskFragment -> {
+ if (!taskFragment.sleepIfPossible(shuttingDown)) {
+ sleepInProgress[0]++;
}
- }
-
- if (prev != null) {
- prev.resumeKeyDispatchingLocked();
- }
-
- mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
-
- // Notify when the task stack has changed, but only if visibilities changed (not just
- // focus). Also if there is an active root pinned task - we always want to notify it about
- // task stack changes, because its positioning may depend on it.
- if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
- || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
- mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
- mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
- }
+ }, true /* traverseTopToBottom */);
+ return sleepInProgress[0] == 0;
}
boolean isTopRootTaskInDisplayArea() {
@@ -5968,10 +4841,10 @@ class Task extends WindowContainer<WindowContainer> {
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
* @param preserveWindows Flag indicating whether windows should be preserved when updating
- * configuration in {@link mEnsureActivitiesVisibleHelper}.
+ * configuration in {@link EnsureActivitiesVisibleHelper}.
* @param configChanges Parts of the configuration that changed for this activity for evaluating
* if the screen should be frozen as part of
- * {@link mEnsureActivitiesVisibleHelper}.
+ * {@link EnsureActivitiesVisibleHelper}.
*
*/
void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
@@ -5987,25 +4860,22 @@ class Task extends WindowContainer<WindowContainer> {
* The activity is either starting or resuming.
* Caller should ensure starting activity is visible.
* @param notifyClients Flag indicating whether the visibility updates should be sent to the
- * clients in {@link mEnsureActivitiesVisibleHelper}.
+ * clients in {@link EnsureActivitiesVisibleHelper}.
* @param preserveWindows Flag indicating whether windows should be preserved when updating
- * configuration in {@link mEnsureActivitiesVisibleHelper}.
+ * configuration in {@link EnsureActivitiesVisibleHelper}.
* @param configChanges Parts of the configuration that changed for this activity for evaluating
* if the screen should be frozen as part of
- * {@link mEnsureActivitiesVisibleHelper}.
+ * {@link EnsureActivitiesVisibleHelper}.
*/
// TODO: Should be re-worked based on the fact that each task as a root task in most cases.
void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mTaskSupervisor.beginActivityVisibilityUpdate();
try {
- forAllLeafTasks(task -> task.mEnsureActivitiesVisibleHelper.process(
- starting, configChanges, preserveWindows, notifyClients),
- true /* traverseTopToBottom */);
-
- // Notify WM shell that task visibilities may have changed
- forAllTasks(task -> task.dispatchTaskInfoChangedIfNeeded(/* force */ false),
- true /* traverseTopToBottom */);
+ forAllLeafTasks(task -> {
+ task.updateActivityVisibilities(starting, configChanges, preserveWindows,
+ notifyClients);
+ }, true /* traverseTopToBottom */);
if (mTranslucentActivityWaiting != null &&
mUndrawnActivitiesBelowTopTranslucent.isEmpty()) {
@@ -6076,25 +4946,6 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- /** @see ActivityRecord#cancelInitializing() */
- void cancelInitializingActivities() {
- // We don't want to clear starting window for activities that aren't behind fullscreen
- // activities as we need to display their starting window until they are done initializing.
- checkBehindFullscreenActivity(null /* toCheck */, ActivityRecord::cancelInitializing);
- }
-
- /**
- * If an activity {@param toCheck} is given, this method returns {@code true} if the activity
- * is occluded by any fullscreen activity. If there is no {@param toCheck} and the handling
- * function {@param handleBehindFullscreenActivity} is given, this method will pass all occluded
- * activities to the function.
- */
- boolean checkBehindFullscreenActivity(ActivityRecord toCheck,
- Consumer<ActivityRecord> handleBehindFullscreenActivity) {
- return mCheckBehindFullscreenActivityHelper.process(
- toCheck, handleBehindFullscreenActivity);
- }
-
/**
* Ensure that the top activity in the root task is resumed.
*
@@ -6135,7 +4986,8 @@ class Task extends WindowContainer<WindowContainer> {
if (!child.isTopActivityFocusable()) {
continue;
}
- if (child.getVisibility(null /* starting */) != TASK_VISIBILITY_VISIBLE) {
+ if (child.getVisibility(null /* starting */)
+ != TASK_FRAGMENT_VISIBILITY_VISIBLE) {
break;
}
@@ -6182,409 +5034,25 @@ class Task extends WindowContainer<WindowContainer> {
return false;
}
- // Find the next top-most activity to resume in this root task that is not finishing and is
- // focusable. If it is not focusable, we will fall into the case below to resume the
- // top activity in the next focusable task.
- ActivityRecord next = topRunningActivity(true /* focusableOnly */);
-
- final boolean hasRunningActivity = next != null;
-
- // TODO: Maybe this entire condition can get removed?
- if (hasRunningActivity && !isAttached()) {
- return false;
- }
-
- mRootWindowContainer.cancelInitializingActivities();
-
- if (!hasRunningActivity) {
- // There are no activities left in the root task, let's look somewhere else.
+ final ActivityRecord topActivity = topRunningActivity(true /* focusableOnly */);
+ if (topActivity == null) {
+ // There are no activities left in this task, let's look somewhere else.
return resumeNextFocusableActivityWhenRootTaskIsEmpty(prev, options);
}
- next.delayedResume = false;
- final TaskDisplayArea taskDisplayArea = getDisplayArea();
-
- // If the top activity is the resumed one, nothing to do.
- if (mResumedActivity == next && next.isState(RESUMED)
- && taskDisplayArea.allResumedActivitiesComplete()) {
- // Make sure we have executed any pending transitions, since there
- // should be nothing left to do at this point.
- executeAppTransition(options);
- // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible
- // we still want to check if the visibility of other windows have changed (e.g. bringing
- // a fullscreen window forward to cover another freeform activity.)
- if (taskDisplayArea.inMultiWindowMode()) {
- taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
- false /* preserveWindows */, true /* notifyClients */);
- }
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity "
- + "resumed %s", next);
- return false;
- }
-
- if (!next.canResumeByCompat()) {
- return false;
- }
-
- // If we are currently pausing an activity, then don't do anything until that is done.
- final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
- if (!allPausedComplete) {
- ProtoLog.v(WM_DEBUG_STATES,
- "resumeTopActivityLocked: Skip resume: some activity pausing.");
-
- return false;
- }
-
- // If we are sleeping, and there is no resumed activity, and the top activity is paused,
- // well that is the state we want.
- if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
- // Make sure we have executed any pending transitions, since there
- // should be nothing left to do at this point.
- executeAppTransition(options);
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Going to sleep and"
- + " all paused");
- return false;
- }
-
- // Make sure that the user who owns this activity is started. If not,
- // we will just leave it as is because someone should be bringing
- // another user's activities to the top of the stack.
- if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) {
- Slog.w(TAG, "Skipping resume of top activity " + next
- + ": user " + next.mUserId + " is stopped");
- return false;
- }
-
- // The activity may be waiting for stop, but that is no longer
- // appropriate for it.
- mTaskSupervisor.mStoppingActivities.remove(next);
-
- if (!next.translucentWindowLaunch)
- next.launching = true;
-
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
-
- //Trigger Activity Resume
- if (mActivityTrigger != null) {
- mActivityTrigger.activityResumeTrigger(next.intent, next.info,
- next.info.applicationInfo,
- next.occludesParent());
- }
-
- if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) {
- mActivityPluginDelegate.activityInvokeNotification
- (next.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
- }
-
- mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);
-
- ActivityRecord lastResumed = null;
- final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
- if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTask()) {
- // So, why aren't we using prev here??? See the param comment on the method. prev
- // doesn't represent the last resumed activity. However, the last focus stack does if
- // it isn't null.
- lastResumed = lastFocusedRootTask.getResumedActivity();
- }
-
- boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
- if (mResumedActivity != null) {
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Pausing %s", mResumedActivity);
- pausing |= startPausingLocked(false /* uiSleeping */, next,
- "resumeTopActivityInnerLocked");
- }
- if (pausing) {
- ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivityLocked: Skip resume: need to"
- + " start pausing");
- // At this point we want to put the upcoming activity's process
- // at the top of the LRU list, since we know we will be needing it
- // very soon and it would be a waste to let it get killed if it
- // happens to be sitting towards the end.
- if (next.attachedToProcess()) {
- next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
- true /* activityChange */, false /* updateOomAdj */,
- false /* addPendingTopUid */);
- } else if (!next.isProcessRunning()) {
- // Since the start-process is asynchronous, if we already know the process of next
- // activity isn't running, we can start the process earlier to save the time to wait
- // for the current activity to be paused.
- final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
- mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
- isTop ? "pre-top-activity" : "pre-activity");
- }
- if (lastResumed != null) {
- lastResumed.setWillCloseOrEnterPip(true);
- }
- return true;
- } else if (mResumedActivity == next && next.isState(RESUMED)
- && taskDisplayArea.allResumedActivitiesComplete()) {
- // It is possible for the activity to be resumed when we paused back stacks above if the
- // next activity doesn't have to wait for pause to complete.
- // So, nothing else to-do except:
- // Make sure we have executed any pending transitions, since there
- // should be nothing left to do at this point.
- executeAppTransition(options);
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Top activity resumed "
- + "(dontWaitForPause) %s", next);
- return true;
- }
-
- // If the most recent activity was noHistory but was only stopped rather
- // than stopped+finished because the device went to sleep, we need to make
- // sure to finish it as we're making a new activity topmost.
- if (shouldSleepActivities()) {
- mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next);
- }
-
- if (prev != null && prev != next && next.nowVisible) {
-
- // The next activity is already visible, so hide the previous
- // activity's windows right now so we can show the new one ASAP.
- // We only do this if the previous is finishing, which should mean
- // it is on top of the one being resumed so hiding it quickly
- // is good. Otherwise, we want to do the normal route of allowing
- // the resumed activity to be shown so we can decide if the
- // previous should actually be hidden depending on whether the
- // new one is found to be full-screen or not.
- if (prev.finishing) {
- prev.setVisibility(false);
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
- "Not waiting for visible to hide: " + prev
- + ", nowVisible=" + next.nowVisible);
- } else {
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
- "Previous already visible but still waiting to hide: " + prev
- + ", nowVisible=" + next.nowVisible);
- }
-
- }
-
- // Launching this app's activity, make sure the app is no longer
- // considered stopped.
- try {
- mTaskSupervisor.getActivityMetricsLogger()
- .notifyBeforePackageUnstopped(next.packageName);
- mAtmService.getPackageManager().setPackageStoppedState(
- next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
- } catch (RemoteException e1) {
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Failed trying to unstop package "
- + next.packageName + ": " + e);
- }
-
- // We are starting up the next activity, so tell the window manager
- // that the previous one will be hidden soon. This way it can know
- // to ignore it when computing the desired screen orientation.
- boolean anim = true;
- final DisplayContent dc = taskDisplayArea.mDisplayContent;
- if (mPerf == null) {
- mPerf = new BoostFramework();
- }
- if (prev != null) {
- if (prev.finishing) {
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
- "Prepare close transition: prev=" + prev);
- if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
- anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- if(prev.getTask() != next.getTask() && mPerf != null) {
- mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST,
- next.packageName);
- }
- dc.prepareAppTransition(TRANSIT_CLOSE);
- }
- prev.setVisibility(false);
- } else {
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
- "Prepare open transition: prev=" + prev);
- if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
- anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- if(prev.getTask() != next.getTask() && mPerf != null) {
- mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST,
- next.packageName);
- }
- dc.prepareAppTransition(TRANSIT_OPEN,
- next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
- }
- }
- } else {
- if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
- if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
- anim = false;
- dc.prepareAppTransition(TRANSIT_NONE);
- } else {
- dc.prepareAppTransition(TRANSIT_OPEN);
- }
- }
-
- if (anim) {
- next.applyOptionsAnimation();
- } else {
- next.abortAndClearOptionsAnimation();
- }
-
- mTaskSupervisor.mNoAnimActivities.clear();
-
- if (next.attachedToProcess()) {
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next
- + " stopped=" + next.stopped
- + " visibleRequested=" + next.mVisibleRequested);
-
- // If the previous activity is translucent, force a visibility update of
- // the next activity, so that it's added to WM's opening app list, and
- // transition animation can be set up properly.
- // For example, pressing Home button with a translucent activity in focus.
- // Launcher is already visible in this case. If we don't add it to opening
- // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
- // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
- final boolean lastActivityTranslucent = lastFocusedRootTask != null
- && (lastFocusedRootTask.inMultiWindowMode()
- || (lastFocusedRootTask.mLastPausedActivity != null
- && !lastFocusedRootTask.mLastPausedActivity.occludesParent()));
-
- // This activity is now becoming visible.
- if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
- next.setVisibility(true);
- }
-
- // schedule launch ticks to collect information about slow apps.
- next.startLaunchTickingLocked();
-
- ActivityRecord lastResumedActivity =
- lastFocusedRootTask == null ? null : lastFocusedRootTask.getResumedActivity();
- final ActivityState lastState = next.getState();
-
- mAtmService.updateCpuStats();
-
- ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);
-
- next.setState(RESUMED, "resumeTopActivityInnerLocked");
-
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order.
- boolean notUpdated = true;
-
- // Activity should also be visible if set mLaunchTaskBehind to true (see
- // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
- if (shouldBeVisible(next)) {
- // We have special rotation behavior when here is some active activity that
- // requests specific orientation or Keyguard is locked. Make sure all activity
- // visibilities are set correctly as well as the transition is updated if needed
- // to get the correct rotation behavior. Otherwise the following call to update
- // the orientation may cause incorrect configurations delivered to client as a
- // result of invisible window resize.
- // TODO: Remove this once visibilities are set correctly immediately when
- // starting an activity.
- notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
- true /* markFrozenIfConfigChanged */, false /* deferResume */);
- }
-
- if (notUpdated) {
- // The configuration update wasn't able to keep the existing
- // instance of the activity, and instead started a new one.
- // We should be all done, but let's just make sure our activity
- // is still at the top and schedule another run if something
- // weird happened.
- ActivityRecord nextNext = topRunningActivity();
- ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
- + "%s, new next: %s", next, nextNext);
- if (nextNext != next) {
- // Do over!
- mTaskSupervisor.scheduleResumeTopActivities();
- }
- if (!next.mVisibleRequested || next.stopped) {
- next.setVisibility(true);
- }
- next.completeResumeLocked();
- return true;
- }
-
- try {
- final ClientTransaction transaction =
- ClientTransaction.obtain(next.app.getThread(), next.appToken);
- // Deliver all pending results.
- ArrayList<ResultInfo> a = next.results;
- if (a != null) {
- final int N = a.size();
- if (!next.finishing && N > 0) {
- if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
- "Delivering results to " + next + ": " + a);
- transaction.addCallback(ActivityResultItem.obtain(a));
- }
- }
-
- if (next.newIntents != null) {
- transaction.addCallback(
- NewIntentItem.obtain(next.newIntents, true /* resume */));
- }
-
- // Well the app will no longer be stopped.
- // Clear app token stopped state in window manager if needed.
- next.notifyAppResumed(next.stopped);
-
- EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
- next.getTask().mTaskId, next.shortComponentName);
-
- mAtmService.getAppWarningsLocked().onResumeActivity(next);
- next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
- next.abortAndClearOptionsAnimation();
- transaction.setLifecycleStateRequest(
- ResumeActivityItem.obtain(next.app.getReportedProcState(),
- dc.isNextTransitionForward()));
- mAtmService.getLifecycleManager().scheduleTransaction(transaction);
-
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Resumed %s", next);
- } catch (Exception e) {
- // Whoops, need to restart this activity!
- ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
- + "%s", lastState, next);
- next.setState(lastState, "resumeTopActivityInnerLocked");
-
- // lastResumedActivity being non-null implies there is a lastStack present.
- if (lastResumedActivity != null) {
- lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
- }
-
- Slog.i(TAG, "Restarting because process died: " + next);
- if (!next.hasBeenLaunched) {
- next.hasBeenLaunched = true;
- } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
- && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
- next.showStartingWindow(false /* taskSwitch */);
- }
- mTaskSupervisor.startSpecificActivity(next, true, false);
- return true;
- }
-
- // From this point on, if something goes wrong there is no way
- // to recover the activity.
- try {
- next.completeResumeLocked();
- } catch (Exception e) {
- // If any exception gets thrown, toss away this
- // activity and try the next one.
- Slog.w(TAG, "Exception thrown during resume of " + next, e);
- next.finishIfPossible("resume-exception", true /* oomAdj */);
- return true;
+ final boolean[] resumed = new boolean[1];
+ final TaskFragment topFragment = topActivity.getTaskFragment();
+ resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause);
+ forAllLeafTaskFragments(f -> {
+ if (topFragment == f) {
+ return;
}
- } else {
- // Whoops, need to restart this activity!
- if (!next.hasBeenLaunched) {
- next.hasBeenLaunched = true;
- } else {
- if (SHOW_APP_STARTING_PREVIEW) {
- next.showStartingWindow(false /* taskSwich */);
- }
- if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
+ if (!f.canBeResumed(null /* starting */)) {
+ return;
}
- ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivityLocked: Restarting %s", next);
- mTaskSupervisor.startSpecificActivity(next, true, true);
- }
-
- return true;
+ resumed[0] |= f.resumeTopActivity(prev, options, deferPause);
+ }, true);
+ return resumed[0];
}
/**
@@ -6618,7 +5086,7 @@ class Task extends WindowContainer<WindowContainer> {
}
void startActivityLocked(ActivityRecord r, @Nullable ActivityRecord focusedTopActivity,
- boolean newTask, boolean keepCurTransition, ActivityOptions options,
+ boolean newTask, boolean isTaskSwitch, ActivityOptions options,
@Nullable ActivityRecord sourceRecord) {
Task rTask = r.getTask();
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
@@ -6664,7 +5132,6 @@ class Task extends WindowContainer<WindowContainer> {
// Slot the activity into the history root task and proceed
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
+ "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
- task.positionChildAtTop(r);
if (mActivityPluginDelegate != null) {
mActivityPluginDelegate.activityInvokeNotification
@@ -6730,21 +5197,18 @@ class Task extends WindowContainer<WindowContainer> {
// "has the same starting icon" as the next one. This allows the
// window manager to keep the previous window it had previously
// created, if it still had one.
- Task prevTask = r.getTask();
- ActivityRecord prev = prevTask.topActivityWithStartingWindow();
- if (prev != null) {
- // We don't want to reuse the previous starting preview if:
- // (1) The current activity is in a different task.
- if (prev.getTask() != prevTask) {
- prev = null;
- }
- // (2) The current activity is already displayed.
- else if (prev.nowVisible) {
- prev = null;
- }
+ Task baseTask = r.getTask();
+ if (baseTask.isEmbedded()) {
+ // If the task is embedded in a task fragment, there may have an existing
+ // starting window in the parent task. This allows the embedded activities
+ // to share the starting window and make sure that the window can have top
+ // z-order by transferring to the top activity.
+ baseTask = baseTask.getParent().asTaskFragment().getTask();
}
- r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity),
+ final ActivityRecord prev = baseTask.getActivity(
+ a -> a.mStartingData != null && a.showToCurrentUser());
+ r.showStartingWindow(prev, newTask, isTaskSwitch,
true /* startActivity */, sourceRecord);
}
} else {
@@ -6778,10 +5242,6 @@ class Task extends WindowContainer<WindowContainer> {
return true;
}
- private boolean isTaskSwitch(ActivityRecord r, ActivityRecord topFocusedActivity) {
- return topFocusedActivity != null && r.getTask() != topFocusedActivity.getTask();
- }
-
/**
* Reset the task by reparenting the activities that have same affinity to the task or
* reparenting the activities that have different affinityies out of the task, while these
@@ -6839,7 +5299,6 @@ class Task extends WindowContainer<WindowContainer> {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
Task finishedTask = r.getTask();
- mDisplayContent.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
r.finishIfPossible(reason, false /* oomAdj */);
@@ -6897,18 +5356,6 @@ class Task extends WindowContainer<WindowContainer> {
return true;
}
- /** Finish all activities in the root task without waiting. */
- void finishAllActivitiesImmediately() {
- if (!hasChild()) {
- removeIfPossible("finishAllActivitiesImmediately");
- return;
- }
- forAllActivities((r) -> {
- Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r);
- r.destroyIfPossible("finishAllActivitiesImmediately");
- });
- }
-
/** @return true if the root task behind this one is a standard activity type. */
private boolean inFrontOfStandardRootTask() {
final TaskDisplayArea taskDisplayArea = getDisplayArea();
@@ -7131,7 +5578,7 @@ class Task extends WindowContainer<WindowContainer> {
// Don't refocus if invisible to current user
final ActivityRecord top = tr.getTopNonFinishingActivity();
- if (top == null || !top.okToShowLocked()) {
+ if (top == null || !top.showToCurrentUser()) {
positionChildAtTop(tr);
if (top != null) {
mTaskSupervisor.mRecentTasks.add(top.getTask());
@@ -7219,7 +5666,6 @@ class Task extends WindowContainer<WindowContainer> {
// Skip the transition for pinned task.
if (!inPinnedWindowingMode()) {
- mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_TO_BACK, tr);
}
moveToBack("moveTaskToBackLocked", tr);
@@ -7245,13 +5691,6 @@ class Task extends WindowContainer<WindowContainer> {
return true;
}
- /**
- * Ensures all visible activities at or below the input activity have the right configuration.
- */
- void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
- mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
- }
-
// TODO: Can only be called from special methods in ActivityTaskSupervisor.
// Need to consolidate those calls points into this resize method so anyone can call directly.
void resize(Rect displayedBounds, boolean preserveWindows, boolean deferResume) {
@@ -7305,114 +5744,32 @@ class Task extends WindowContainer<WindowContainer> {
}
}
- /**
- * Reset local parameters because an app's activity died.
- * @param app The app of the activity that died.
- * @return {@code true} if the process of the pausing activity is died.
- */
- boolean handleAppDied(WindowProcessController app) {
- warnForNonLeafTask("handleAppDied");
- boolean isPausingDied = false;
- if (mPausingActivity != null && mPausingActivity.app == app) {
- ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
- mPausingActivity);
- mPausingActivity = null;
- isPausingDied = true;
- }
- if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
- if (mLastPausedActivity.isNoHistory()) {
- mTaskSupervisor.mNoHistoryActivities.remove(mLastPausedActivity);
- }
- mLastPausedActivity = null;
- }
- return isPausingDied;
- }
-
boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
String dumpPackage, final boolean needSep) {
- Runnable headerPrinter = () -> {
- if (needSep) {
- pw.println();
- }
- pw.println(" RootTask #" + getRootTaskId()
- + ": type=" + activityTypeToString(getActivityType())
- + " mode=" + windowingModeToString(getWindowingMode()));
- pw.println(" isSleeping=" + shouldSleepActivities());
- pw.println(" mBounds=" + getRequestedOverrideBounds());
- pw.println(" mCreatedByOrganizer=" + mCreatedByOrganizer);
- };
-
- boolean printed = false;
+ return dump(" ", fd, pw, dumpAll, dumpClient, dumpPackage, needSep, null /* header */);
+ }
- if (dumpPackage == null) {
- // If we are not filtering by package, we want to print absolutely everything,
- // so always print the header even if there are no tasks/activities inside.
- headerPrinter.run();
- headerPrinter = null;
- printed = true;
+ @Override
+ void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
+ super.dumpInner(prefix, pw, dumpAll, dumpPackage);
+ if (mCreatedByOrganizer) {
+ pw.println(prefix + " mCreatedByOrganizer=true");
}
-
- printed |= printThisActivity(pw, getPausingActivity(), dumpPackage, false,
- " mPausingActivity: ", null);
- printed |= printThisActivity(pw, getResumedActivity(), dumpPackage, false,
- " mResumedActivity: ", null);
- if (dumpAll) {
- printed |= printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
- " mLastPausedActivity: ", null);
+ if (mLastNonFullscreenBounds != null) {
+ pw.print(prefix); pw.print(" mLastNonFullscreenBounds=");
+ pw.println(mLastNonFullscreenBounds);
}
-
- printed |= dumpActivities(fd, pw, dumpAll, dumpClient, dumpPackage, false, headerPrinter);
-
- return printed;
- }
-
- private boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
- boolean dumpClient, String dumpPackage, boolean needSep, Runnable header) {
- if (!hasChild()) {
- return false;
+ if (isLeafTask()) {
+ pw.println(prefix + " isSleeping=" + shouldSleepActivities());
+ printThisActivity(pw, getTopPausingActivity(), dumpPackage, false,
+ prefix + " topPausingActivity=", null);
+ printThisActivity(pw, getTopResumedActivity(), dumpPackage, false,
+ prefix + " topResumedActivity=", null);
+ if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
+ pw.print(prefix); pw.print(" mMinWidth="); pw.print(mMinWidth);
+ pw.print(" mMinHeight="); pw.println(mMinHeight);
+ }
}
- final AtomicBoolean printedHeader = new AtomicBoolean(false);
- final AtomicBoolean printed = new AtomicBoolean(false);
- forAllLeafTasks((task) -> {
- final String prefix = " ";
- Runnable headerPrinter = () -> {
- printed.set(true);
- if (!printedHeader.get()) {
- if (needSep) {
- pw.println("");
- }
- if (header != null) {
- header.run();
- }
- printedHeader.set(true);
- }
- pw.print(prefix); pw.print("* "); pw.println(task);
- pw.print(prefix); pw.print(" mBounds=");
- pw.println(task.getRequestedOverrideBounds());
- pw.print(prefix); pw.print(" mMinWidth="); pw.print(task.mMinWidth);
- pw.print(" mMinHeight="); pw.println(task.mMinHeight);
- if (mLastNonFullscreenBounds != null) {
- pw.print(prefix);
- pw.print(" mLastNonFullscreenBounds=");
- pw.println(task.mLastNonFullscreenBounds);
- }
- task.dump(pw, prefix + " ");
- };
- if (dumpPackage == null) {
- // If we are not filtering by package, we want to print absolutely everything,
- // so always print the header even if there are no activities inside.
- headerPrinter.run();
- headerPrinter = null;
- }
- final ArrayList<ActivityRecord> activities = new ArrayList<>();
- // Add activities by traversing the hierarchy from bottom to top, since activities
- // are dumped in reverse order in {@link ActivityTaskSupervisor#dumpHistoryList()}.
- task.forAllActivities((Consumer<ActivityRecord>) activities::add,
- false /* traverseTopToBottom */);
- dumpHistoryList(fd, pw, activities, prefix, "Hist", true, !dumpAll, dumpClient,
- dumpPackage, false, headerPrinter, task);
- }, true /* traverseTopToBottom */);
- return printed.get();
}
ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
@@ -7798,15 +6155,6 @@ class Task extends WindowContainer<WindowContainer> {
getDisplayContent().getPinnedTaskController().setActions(actions);
}
- /** Returns true if a removal action is still being deferred. */
- boolean handleCompleteDeferredRemoval() {
- if (isAnimating(TRANSITION | CHILDREN)) {
- return true;
- }
-
- return super.handleCompleteDeferredRemoval();
- }
-
public DisplayInfo getDisplayInfo() {
return mDisplayContent.getDisplayInfo();
}
@@ -7822,6 +6170,7 @@ class Task extends WindowContainer<WindowContainer> {
}
}
+ @Override
void executeAppTransition(ActivityOptions options) {
mDisplayContent.executeAppTransition();
ActivityOptions.abort(options);
@@ -7844,10 +6193,6 @@ class Task extends WindowContainer<WindowContainer> {
return display != null ? display.isSleeping() : mAtmService.isSleepingLocked();
}
- boolean shouldSleepOrShutDownActivities() {
- return shouldSleepActivities() || mAtmService.mShuttingDown;
- }
-
private Rect getRawBounds() {
return super.getBounds();
}
@@ -7866,14 +6211,12 @@ class Task extends WindowContainer<WindowContainer> {
}
final long token = proto.start(fieldId);
- super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
proto.write(TaskProto.ID, mTaskId);
- proto.write(DISPLAY_ID, getDisplayId());
proto.write(ROOT_TASK_ID, getRootTaskId());
- if (mResumedActivity != null) {
- mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+ if (getTopResumedActivity() != null) {
+ getTopResumedActivity().writeIdentifierToProto(proto, RESUMED_ACTIVITY);
}
if (realActivity != null) {
proto.write(REAL_ACTIVITY, realActivity.flattenToShortString());
@@ -7881,11 +6224,7 @@ class Task extends WindowContainer<WindowContainer> {
if (origActivity != null) {
proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
}
- proto.write(ACTIVITY_TYPE, getActivityType());
proto.write(RESIZE_MODE, mResizeMode);
- proto.write(MIN_WIDTH, mMinWidth);
- proto.write(MIN_HEIGHT, mMinHeight);
-
proto.write(FILLS_PARENT, matchParentBounds());
getRawBounds().dumpDebug(proto, BOUNDS);
@@ -7902,6 +6241,8 @@ class Task extends WindowContainer<WindowContainer> {
proto.write(AFFINITY, affinity);
proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null);
+ super.dumpDebug(proto, TASK_FRAGMENT, logLevel);
+
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 89db2ff83e6f..e0e287699299 100755
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -32,15 +32,12 @@ 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;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+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;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -50,6 +47,7 @@ import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.UserHandle;
import android.util.IntArray;
@@ -101,13 +99,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
private int mColorLayerCounter = 0;
/**
- * A control placed at the appropriate level for transitions to occur.
- */
- private SurfaceControl mAppAnimationLayer;
- private SurfaceControl mBoostedAppAnimationLayer;
- private SurfaceControl mHomeAppAnimationLayer;
-
- /**
* Given that the split-screen divider does not have an AppWindowToken, it
* will have to live inside of a "NonAppWindowContainer". However, in visual Z order
* it will need to be interleaved with some of our children, appearing on top of
@@ -134,8 +125,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>();
private final IntArray mTmpNeedsZBoostIndexes = new IntArray();
- private int mTmpLayerForSplitScreenDividerAnchor;
- private int mTmpLayerForAnimationLayer;
private ArrayList<Task> mTmpTasks = new ArrayList<>();
@@ -159,7 +148,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
/**
* A launch root task for activity launching with {@link FLAG_ACTIVITY_LAUNCH_ADJACENT} flag.
*/
- private Task mLaunchAdjacentFlagRootTask;
+ @VisibleForTesting
+ Task mLaunchAdjacentFlagRootTask;
/**
* A focusable root task that is purposely to be positioned at the top. Although the root
@@ -490,7 +480,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// Update the top resumed activity because the preferred top focusable task may be changed.
mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded();
- final ActivityRecord r = child.getResumedActivity();
+ final ActivityRecord r = child.getTopResumedActivity();
if (r != null && r == mRootWindowContainer.getTopResumedActivity()) {
mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt");
}
@@ -802,10 +792,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
return SCREEN_ORIENTATION_UNSPECIFIED;
} else {
- // Apps and their containers are not allowed to specify an orientation of full screen
- // tasks created by organizer. The organizer handles the orientation instead.
- final Task task = getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
- if (task != null && task.isVisible() && task.mCreatedByOrganizer) {
+ // Apps and their containers are not allowed to specify an orientation of non floating
+ // visible tasks created by organizer. The organizer handles the orientation instead.
+ final Task nonFloatingTopTask =
+ getRootTask(t -> !t.getWindowConfiguration().tasksAreFloating());
+ if (nonFloatingTopTask != null && nonFloatingTopTask.mCreatedByOrganizer
+ && nonFloatingTopTask.isVisible()) {
return SCREEN_ORIENTATION_UNSPECIFIED;
}
}
@@ -872,36 +864,14 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
int layer = 0;
// Place root home tasks to the bottom.
- layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer, false /* normalRootTasks */);
- // The home animation layer is between the root home tasks and the normal root tasks.
- final int layerForHomeAnimationLayer = layer++;
- mTmpLayerForSplitScreenDividerAnchor = layer++;
- mTmpLayerForAnimationLayer = layer++;
- layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer, true /* normalRootTasks */);
-
- // The boosted animation layer is between the normal root tasks and the always on top
- // root tasks.
- final int layerForBoostedAnimationLayer = layer++;
- adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer, false /* normalRootTasks */);
-
- t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
- t.setLayer(mAppAnimationLayer, mTmpLayerForAnimationLayer);
- t.setLayer(mSplitScreenDividerAnchor, mTmpLayerForSplitScreenDividerAnchor);
- t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
- }
-
- private int adjustNormalRootTaskLayer(WindowContainer child, int layer) {
- if (child.asTask() != null && child.inSplitScreenWindowingMode()) {
- // The split screen divider anchor is located above the split screen window.
- mTmpLayerForSplitScreenDividerAnchor = layer++;
- }
- if ((child.asTask() != null && child.asTask().isAnimatingByRecents())
- || child.isAppTransitioning()) {
- // The animation layer is located above the highest animating root task and no
- // higher.
- mTmpLayerForAnimationLayer = layer++;
- }
- return layer;
+ layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer);
+ layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer);
+ // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and
+ // make app pair split only have single root then we can just attach the
+ // divider to the single root task in shell.
+ layer = Math.max(layer, SPLIT_DIVIDER_LAYER + 1);
+ adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
+ t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
}
/**
@@ -910,38 +880,45 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
* normal rootTasks.
*
* @param startLayer The beginning layer of this group of rootTasks.
- * @param normalRootTasks Set {@code true} if this group is neither home nor always on top.
* @return The adjusted layer value.
*/
private int adjustRootTaskLayer(SurfaceControl.Transaction t,
- ArrayList<WindowContainer> children, int startLayer, boolean normalRootTasks) {
+ ArrayList<WindowContainer> children, int startLayer) {
mTmpNeedsZBoostIndexes.clear();
final int childCount = children.size();
+ boolean hasAdjacentTask = false;
for (int i = 0; i < childCount; i++) {
final WindowContainer child = children.get(i);
final TaskDisplayArea childTda = child.asTaskDisplayArea();
-
- boolean childNeedsZBoost = childTda != null
+ final boolean childNeedsZBoost = childTda != null
? childTda.childrenNeedZBoost()
: child.needsZBoost();
- if (!childNeedsZBoost) {
- child.assignLayer(t, startLayer++);
- if (normalRootTasks) {
- startLayer = adjustNormalRootTaskLayer(child, startLayer);
- }
- } else {
+ if (childNeedsZBoost) {
mTmpNeedsZBoostIndexes.add(i);
+ continue;
+ }
+
+ final Task childTask = child.asTask();
+ final boolean inAdjacentTask = childTask != null
+ && child.inMultiWindowMode()
+ && childTask.getRootTask().getAdjacentTaskFragment() != null;
+
+ if (inAdjacentTask || child.inSplitScreenWindowingMode()) {
+ hasAdjacentTask = true;
+ } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) {
+ // Task on top of adjacent tasks should be higher than split divider layer so
+ // set it as start.
+ startLayer = SPLIT_DIVIDER_LAYER + 1;
}
+
+ child.assignLayer(t, startLayer++);
}
final int zBoostSize = mTmpNeedsZBoostIndexes.size();
for (int i = 0; i < zBoostSize; i++) {
final WindowContainer child = children.get(mTmpNeedsZBoostIndexes.get(i));
child.assignLayer(t, startLayer++);
- if (normalRootTasks) {
- startLayer = adjustNormalRootTaskLayer(child, startLayer);
- }
}
return startLayer;
}
@@ -955,19 +932,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
@Override
- SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
- switch (animationLayer) {
- case ANIMATION_LAYER_BOOSTED:
- return mBoostedAppAnimationLayer;
- case ANIMATION_LAYER_HOME:
- return mHomeAppAnimationLayer;
- case ANIMATION_LAYER_STANDARD:
- default:
- return mAppAnimationLayer;
- }
- }
-
- @Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = getTopMostActivity();
@@ -987,42 +951,21 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
.setName("colorBackgroundLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
- mAppAnimationLayer = makeChildSurface(null)
- .setName("animationLayer")
- .setCallsite("TaskDisplayArea.onParentChanged")
- .build();
- mBoostedAppAnimationLayer = makeChildSurface(null)
- .setName("boostedAnimationLayer")
- .setCallsite("TaskDisplayArea.onParentChanged")
- .build();
- mHomeAppAnimationLayer = makeChildSurface(null)
- .setName("homeAnimationLayer")
- .setCallsite("TaskDisplayArea.onParentChanged")
- .build();
mSplitScreenDividerAnchor = makeChildSurface(null)
.setName("splitScreenDividerAnchor")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
getSyncTransaction()
- .show(mAppAnimationLayer)
- .show(mBoostedAppAnimationLayer)
- .show(mHomeAppAnimationLayer)
.show(mSplitScreenDividerAnchor);
});
} else {
super.onParentChanged(newParent, oldParent);
mWmService.mTransactionFactory.get()
.remove(mColorBackgroundLayer)
- .remove(mAppAnimationLayer)
- .remove(mBoostedAppAnimationLayer)
- .remove(mHomeAppAnimationLayer)
.remove(mSplitScreenDividerAnchor)
.apply();
mColorBackgroundLayer = null;
- mAppAnimationLayer = null;
- mBoostedAppAnimationLayer = null;
- mHomeAppAnimationLayer = null;
mSplitScreenDividerAnchor = null;
}
}
@@ -1063,15 +1006,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
@Override
void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
super.migrateToNewSurfaceControl(t);
- if (mAppAnimationLayer == null) {
+ if (mColorBackgroundLayer == null) {
return;
}
// As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
t.reparent(mColorBackgroundLayer, mSurfaceControl);
- t.reparent(mAppAnimationLayer, mSurfaceControl);
- t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
- t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
t.reparent(mSplitScreenDividerAnchor, mSurfaceControl);
reassignLayer(t);
scheduleAnimation();
@@ -1160,29 +1100,27 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return rootTask;
}
} else if (candidateTask != null) {
- final Task rootTask = candidateTask;
final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options,
sourceTask, launchFlags);
-
if (launchRootTask != null) {
- if (rootTask.getParent() == null) {
- launchRootTask.addChild(rootTask, position);
- } else if (rootTask.getParent() != launchRootTask) {
- rootTask.reparent(launchRootTask, position);
+ if (candidateTask.getParent() == null) {
+ launchRootTask.addChild(candidateTask, position);
+ } else if (candidateTask.getParent() != launchRootTask) {
+ candidateTask.reparent(launchRootTask, position);
}
- } else if (rootTask.getDisplayArea() != this || !rootTask.isRootTask()) {
- if (rootTask.getParent() == null) {
- addChild(rootTask, position);
+ } else if (candidateTask.getDisplayArea() != this || !candidateTask.isRootTask()) {
+ if (candidateTask.getParent() == null) {
+ addChild(candidateTask, position);
} else {
- rootTask.reparent(this, onTop);
+ candidateTask.reparent(this, onTop);
}
}
// Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
if (candidateTask.getWindowingMode() != windowingMode) {
candidateTask.setWindowingMode(windowingMode);
}
- return rootTask;
+ return candidateTask.getRootTask();
}
return new Task.Builder(mAtmService)
.setWindowingMode(windowingMode)
@@ -1294,7 +1232,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
+ adjacentFlagRootTask);
}
- if (adjacentFlagRootTask.mAdjacentTask == null) {
+ if (adjacentFlagRootTask.getAdjacentTaskFragment() == null) {
throw new UnsupportedOperationException(
"Can't set non-adjacent root as launch adjacent flag root tr="
+ adjacentFlagRootTask);
@@ -1332,8 +1270,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// If the adjacent launch is coming from the same root, launch to adjacent root instead.
if (sourceTask != null
&& sourceTask.getRootTask().mTaskId == mLaunchAdjacentFlagRootTask.mTaskId
- && mLaunchAdjacentFlagRootTask.mAdjacentTask != null) {
- return mLaunchAdjacentFlagRootTask.mAdjacentTask;
+ && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null) {
+ return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
} else {
return mLaunchAdjacentFlagRootTask;
}
@@ -1342,16 +1280,22 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) {
final Task launchRootTask = mLaunchRootTasks.get(i).task;
- // Return the focusable root task for improving the UX with staged split screen.
- final Task adjacentRootTask = launchRootTask != null
- ? launchRootTask.mAdjacentTask : null;
- if (adjacentRootTask != null && adjacentRootTask.isFocusedRootTaskOnDisplay()) {
+ final TaskFragment adjacentTaskFragment = launchRootTask != null
+ ? launchRootTask.getAdjacentTaskFragment() : null;
+ final Task adjacentRootTask =
+ adjacentTaskFragment != null ? adjacentTaskFragment.asTask() : null;
+ if (sourceTask != null && sourceTask.getRootTask() == adjacentRootTask) {
return adjacentRootTask;
} else {
return launchRootTask;
}
}
}
+ // For better split UX, If task launch by the source task which root task is created by
+ // organizer, it should also launch in that root too.
+ if (sourceTask != null && sourceTask.getRootTask().mCreatedByOrganizer) {
+ return sourceTask.getRootTask();
+ }
return null;
}
@@ -1436,11 +1380,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
// TODO(b/111541062): Move this into Task#getResumedActivity()
// Check if the focused root task has the resumed activity
- ActivityRecord resumedActivity = focusedRootTask.getResumedActivity();
+ ActivityRecord resumedActivity = focusedRootTask.getTopResumedActivity();
if (resumedActivity == null || resumedActivity.app == null) {
// If there is no registered resumed activity in the root task or it is not running -
// try to use previously resumed one.
- resumedActivity = focusedRootTask.getPausingActivity();
+ resumedActivity = focusedRootTask.getTopPausingActivity();
if (resumedActivity == null || resumedActivity.app == null) {
// If previously resumed activity doesn't work either - find the topmost running
// activity that can be focused.
@@ -1467,7 +1411,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// Clear last paused activity if focused root task changed while sleeping, so that the
// top activity of current focused task can be resumed.
if (mDisplayContent.isSleeping()) {
- currentFocusedTask.mLastPausedActivity = null;
+ currentFocusedTask.clearLastPausedActivity();
}
mLastFocusedRootTask = prevFocusedTask;
@@ -1488,7 +1432,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
continue;
}
- final ActivityRecord r = mChildren.get(i).asTask().getResumedActivity();
+ final ActivityRecord r = mChildren.get(i).asTask().getTopResumedActivity();
if (r != null && !r.isState(RESUMED)) {
return false;
}
@@ -1514,18 +1458,28 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
*/
boolean pauseBackTasks(ActivityRecord resuming) {
final int[] someActivityPaused = {0};
- forAllLeafTasks((task) -> {
- final ActivityRecord resumedActivity = task.getResumedActivity();
- if (resumedActivity != null
- && (task.getVisibility(resuming) != TASK_VISIBILITY_VISIBLE
- || !task.isTopActivityFocusable())) {
- ProtoLog.d(WM_DEBUG_STATES, "pauseBackTasks: task=%s "
- + "mResumedActivity=%s", task, resumedActivity);
- if (task.startPausingLocked(false /* uiSleeping*/,
- resuming, "pauseBackTasks")) {
- someActivityPaused[0]++;
+ forAllLeafTasks(leafTask -> {
+ // Check if the direct child resumed activity in the leaf task needed to be paused if
+ // the leaf task is not a leaf task fragment.
+ if (!leafTask.isLeafTaskFragment()) {
+ final ActivityRecord top = topRunningActivity();
+ final ActivityRecord resumedActivity = leafTask.getResumedActivity();
+ if (resumedActivity != null && top.getTaskFragment() != leafTask) {
+ // Pausing the resumed activity because it is occluded by other task fragment.
+ if (leafTask.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
+ someActivityPaused[0]++;
+ }
}
}
+
+ leafTask.forAllLeafTaskFragments((taskFrag) -> {
+ final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
+ if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
+ if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
+ someActivityPaused[0]++;
+ }
+ }
+ }, true /* traverseTopToBottom */);
}, true /* traverseTopToBottom */);
return someActivityPaused[0] > 0;
}
@@ -1753,13 +1707,17 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
* Whether we can show activity requesting the given min width/height in multi window below
* this {@link TaskDisplayArea}.
*/
- boolean supportsActivityMinWidthHeightMultiWindow(int minWidth, int minHeight) {
- final int configRespectsActivityMinWidthHeightMultiWindow =
- mAtmService.mRespectsActivityMinWidthHeightMultiWindow;
+ boolean supportsActivityMinWidthHeightMultiWindow(int minWidth, int minHeight,
+ @Nullable ActivityInfo activityInfo) {
+ if (activityInfo != null && !activityInfo.shouldCheckMinWidthHeightForMultiWindow()) {
+ return true;
+ }
if (minWidth <= 0 && minHeight <= 0) {
// No request min width/height.
return true;
}
+ final int configRespectsActivityMinWidthHeightMultiWindow =
+ mAtmService.mRespectsActivityMinWidthHeightMultiWindow;
if (configRespectsActivityMinWidthHeightMultiWindow == -1) {
// Device override to ignore min width/height.
return true;
@@ -2152,7 +2110,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
if (destroyContentOnRemoval
|| !task.isActivityTypeStandardOrUndefined()
|| task.mCreatedByOrganizer) {
- task.finishAllActivitiesImmediately();
+ task.remove(false /* withTransition */, "removeTaskDisplayArea");
} else {
// Reparent task to corresponding launch root or display area.
final WindowContainer launchRoot =
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
new file mode 100644
index 000000000000..ad51387210d9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -0,0 +1,2541 @@
+/*
+ * 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.wm;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+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;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+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 android.os.Process.INVALID_UID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+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.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
+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.PRESERVE_WINDOWS;
+import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
+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.TaskFragmentProto.ACTIVITY_TYPE;
+import static com.android.server.wm.TaskFragmentProto.DISPLAY_ID;
+import static com.android.server.wm.TaskFragmentProto.MIN_HEIGHT;
+import static com.android.server.wm.TaskFragmentProto.MIN_WIDTH;
+import static com.android.server.wm.TaskFragmentProto.WINDOW_CONTAINER;
+import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
+import static com.android.server.wm.WindowContainerChildProto.TASK_FRAGMENT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.ResultInfo;
+import android.app.WindowConfiguration;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOrganizerToken;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.function.pooled.PooledFunction;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * A basic container that can be used to contain activities or other {@link TaskFragment}, which
+ * also able to manage the activity lifecycle and updates the visibilities of the activities in it.
+ */
+class TaskFragment extends WindowContainer<WindowContainer> {
+ @IntDef(prefix = {"TASK_FRAGMENT_VISIBILITY"}, value = {
+ TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ })
+ @interface TaskFragmentVisibility {}
+
+ /**
+ * TaskFragment is visible. No other TaskFragment(s) on top that fully or partially occlude it.
+ */
+ static final int TASK_FRAGMENT_VISIBILITY_VISIBLE = 0;
+
+ /** TaskFragment is partially occluded by other translucent TaskFragment(s) on top of it. */
+ static final int TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
+
+ /** TaskFragment is completely invisible. */
+ static final int TASK_FRAGMENT_VISIBILITY_INVISIBLE = 2;
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskFragment" : TAG_ATM;
+ private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
+ private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
+ private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
+
+ /** Set to false to disable the preview that is shown while a new activity is being started. */
+ static final boolean SHOW_APP_STARTING_PREVIEW = true;
+
+ /**
+ * Indicate that the minimal width/height should use the default value.
+ *
+ * @see #mMinWidth
+ * @see #mMinHeight
+ */
+ static final int INVALID_MIN_SIZE = -1;
+
+ final ActivityTaskManagerService mAtmService;
+ final ActivityTaskSupervisor mTaskSupervisor;
+ final RootWindowContainer mRootWindowContainer;
+ private final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
+
+ /**
+ * Minimal width of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
+ * should use the default minimal width.
+ */
+ int mMinWidth;
+
+ /**
+ * Minimal height of this task fragment when it's resizeable. {@link #INVALID_MIN_SIZE} means it
+ * should use the default minimal height.
+ */
+ int mMinHeight;
+
+ Dimmer mDimmer = new Dimmer(this);
+
+ /** This task fragment will be removed when the cleanup of its children are done. */
+ private boolean mIsRemovalRequested;
+
+ /** The TaskFragment that is adjacent to this one. */
+ @Nullable
+ private TaskFragment mAdjacentTaskFragment;
+
+ /**
+ * Whether to move adjacent task fragment together when re-positioning.
+ *
+ * @see #mAdjacentTaskFragment
+ */
+ // TODO(b/207185041): Remove this once having a single-top root for split screen.
+ boolean mMoveAdjacentTogether;
+
+ /**
+ * Prevents duplicate calls to onTaskAppeared.
+ */
+ boolean mTaskFragmentAppearedSent;
+
+ /**
+ * The last running activity of the TaskFragment was finished due to clear task while launching
+ * an activity in the Task.
+ */
+ boolean mClearedTaskForReuse;
+
+ /**
+ * When we are in the process of pausing an activity, before starting the
+ * next one, this variable holds the activity that is currently being paused.
+ *
+ * Only set at leaf task fragments.
+ */
+ @Nullable
+ private ActivityRecord mPausingActivity = null;
+
+ /**
+ * This is the last activity that we put into the paused state. This is
+ * used to determine if we need to do an activity transition while sleeping,
+ * when we normally hold the top activity paused.
+ */
+ ActivityRecord mLastPausedActivity = null;
+
+ /**
+ * Current activity that is resumed, or null if there is none.
+ * Only set at leaf task fragments.
+ */
+ @Nullable
+ private ActivityRecord mResumedActivity = null;
+
+ /**
+ * This TaskFragment was created by an organizer which has the following implementations.
+ * <ul>
+ * <li>The TaskFragment won't be removed when it is empty. Removal has to be an explicit
+ * request from the organizer.</li>
+ * <li>If this fragment is a Task object then unlike other non-root tasks, it's direct
+ * children are visible to the organizer for ordering purposes.</li>
+ * <li>A TaskFragment can be created by {@link android.window.TaskFragmentOrganizer}, and
+ * a Task can be created by {@link android.window.TaskOrganizer}.</li>
+ * </ul>
+ */
+ @VisibleForTesting
+ boolean mCreatedByOrganizer;
+
+ /** Whether this TaskFragment is embedded in a task. */
+ private final boolean mIsEmbedded;
+
+ /** Organizer that organizing this TaskFragment. */
+ @Nullable
+ private ITaskFragmentOrganizer mTaskFragmentOrganizer;
+ private int mTaskFragmentOrganizerUid = INVALID_UID;
+ private @Nullable String mTaskFragmentOrganizerProcessName;
+
+ /** Client assigned unique token for this TaskFragment if this is created by an organizer. */
+ @Nullable
+ private IBinder mFragmentToken;
+
+ /**
+ * Whether to delay the last activity of TaskFragment being immediately removed while finishing.
+ * This should only be set on a embedded TaskFragment, where the organizer can have the
+ * opportunity to perform animations and finishing the adjacent TaskFragment.
+ */
+ private boolean mDelayLastActivityRemoval;
+
+ final Point mLastSurfaceSize = new Point();
+
+ private final Rect mTmpInsets = new Rect();
+ private final Rect mTmpBounds = new Rect();
+ private final Rect mTmpFullBounds = new Rect();
+ private final Rect mTmpStableBounds = new Rect();
+ private final Rect mTmpNonDecorBounds = new Rect();
+
+ private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
+ new EnsureActivitiesVisibleHelper(this);
+ private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
+ new EnsureVisibleActivitiesConfigHelper();
+ private class EnsureVisibleActivitiesConfigHelper {
+ private boolean mUpdateConfig;
+ private boolean mPreserveWindow;
+ private boolean mBehindFullscreen;
+
+ void reset(boolean preserveWindow) {
+ mPreserveWindow = preserveWindow;
+ mUpdateConfig = false;
+ mBehindFullscreen = false;
+ }
+
+ void process(ActivityRecord start, boolean preserveWindow) {
+ if (start == null || !start.mVisibleRequested) {
+ return;
+ }
+ reset(preserveWindow);
+
+ final PooledFunction f = PooledLambda.obtainFunction(
+ EnsureVisibleActivitiesConfigHelper::processActivity, this,
+ PooledLambda.__(ActivityRecord.class));
+ forAllActivities(f, start, true /*includeBoundary*/, true /*traverseTopToBottom*/);
+ f.recycle();
+
+ if (mUpdateConfig) {
+ // Ensure the resumed state of the focus activity if we updated the configuration of
+ // any activity.
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
+ }
+
+ boolean processActivity(ActivityRecord r) {
+ mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
+ mBehindFullscreen |= r.occludesParent();
+ return mBehindFullscreen;
+ }
+ }
+
+ /** Creates an embedded task fragment. */
+ TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
+ boolean createdByOrganizer) {
+ this(atmService, fragmentToken, createdByOrganizer, true /* isEmbedded */);
+ }
+
+ TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
+ boolean createdByOrganizer, boolean isEmbedded) {
+ super(atmService.mWindowManager);
+
+ mAtmService = atmService;
+ mTaskSupervisor = mAtmService.mTaskSupervisor;
+ mRootWindowContainer = mAtmService.mRootWindowContainer;
+ mCreatedByOrganizer = createdByOrganizer;
+ mIsEmbedded = isEmbedded;
+ mTaskFragmentOrganizerController =
+ mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
+ mFragmentToken = fragmentToken;
+ mRemoteToken = new RemoteToken(this);
+ }
+
+ @NonNull
+ static TaskFragment fromTaskFragmentToken(@Nullable IBinder token,
+ @NonNull ActivityTaskManagerService service) {
+ if (token == null) return null;
+ return service.mWindowOrganizerController.getTaskFragment(token);
+ }
+
+ void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment, boolean moveTogether) {
+ if (mAdjacentTaskFragment == taskFragment) {
+ return;
+ }
+ resetAdjacentTaskFragment();
+ if (taskFragment != null) {
+ mAdjacentTaskFragment = taskFragment;
+ mMoveAdjacentTogether = moveTogether;
+ taskFragment.setAdjacentTaskFragment(this, moveTogether);
+ }
+ }
+
+ private void resetAdjacentTaskFragment() {
+ // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
+ if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
+ mAdjacentTaskFragment.mAdjacentTaskFragment = null;
+ mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
+ mAdjacentTaskFragment.mMoveAdjacentTogether = false;
+ }
+ mAdjacentTaskFragment = null;
+ mDelayLastActivityRemoval = false;
+ mMoveAdjacentTogether = false;
+ }
+
+ void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
+ @NonNull String processName) {
+ mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
+ mTaskFragmentOrganizerUid = uid;
+ mTaskFragmentOrganizerProcessName = processName;
+ }
+
+ /** Whether this TaskFragment is organized by the given {@code organizer}. */
+ boolean hasTaskFragmentOrganizer(ITaskFragmentOrganizer organizer) {
+ return organizer != null && mTaskFragmentOrganizer != null
+ && organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder());
+ }
+
+ TaskFragment getAdjacentTaskFragment() {
+ return mAdjacentTaskFragment;
+ }
+
+ /** Returns the currently topmost resumed activity. */
+ @Nullable
+ ActivityRecord getTopResumedActivity() {
+ final ActivityRecord taskFragResumedActivity = getResumedActivity();
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ WindowContainer<?> child = getChildAt(i);
+ ActivityRecord topResumedActivity = null;
+ if (taskFragResumedActivity != null && child == taskFragResumedActivity) {
+ topResumedActivity = child.asActivityRecord();
+ } else if (child.asTaskFragment() != null) {
+ topResumedActivity = child.asTaskFragment().getTopResumedActivity();
+ }
+ if (topResumedActivity != null) {
+ return topResumedActivity;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the currently resumed activity in this TaskFragment's
+ * {@link #mChildren direct children}
+ */
+ ActivityRecord getResumedActivity() {
+ return mResumedActivity;
+ }
+
+ void setResumedActivity(ActivityRecord r, String reason) {
+ warnForNonLeafTaskFragment("setResumedActivity");
+ if (mResumedActivity == r) {
+ return;
+ }
+
+ if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+ Slog.d(TAG, "setResumedActivity taskFrag:" + this + " + from: "
+ + mResumedActivity + " to:" + r + " reason:" + reason);
+ }
+ mResumedActivity = r;
+ mTaskSupervisor.updateTopResumedActivityIfNeeded();
+ }
+
+ @VisibleForTesting
+ void setPausingActivity(ActivityRecord pausing) {
+ mPausingActivity = pausing;
+ }
+
+ /** Returns the currently topmost pausing activity. */
+ @Nullable
+ ActivityRecord getTopPausingActivity() {
+ final ActivityRecord taskFragPausingActivity = getPausingActivity();
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ WindowContainer<?> child = getChildAt(i);
+ ActivityRecord topPausingActivity = null;
+ if (taskFragPausingActivity != null && child == taskFragPausingActivity) {
+ topPausingActivity = child.asActivityRecord();
+ } else if (child.asTaskFragment() != null) {
+ topPausingActivity = child.asTaskFragment().getTopPausingActivity();
+ }
+ if (topPausingActivity != null) {
+ return topPausingActivity;
+ }
+ }
+ return null;
+ }
+
+ ActivityRecord getPausingActivity() {
+ return mPausingActivity;
+ }
+
+ int getDisplayId() {
+ final DisplayContent dc = getDisplayContent();
+ return dc != null ? dc.mDisplayId : INVALID_DISPLAY;
+ }
+
+ @Nullable
+ Task getTask() {
+ if (asTask() != null) {
+ return asTask();
+ }
+
+ TaskFragment parent = getParent() != null ? getParent().asTaskFragment() : null;
+ return parent != null ? parent.getTask() : null;
+ }
+
+ @Override
+ @Nullable
+ TaskDisplayArea getDisplayArea() {
+ return (TaskDisplayArea) super.getDisplayArea();
+ }
+
+ @Override
+ public boolean isAttached() {
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
+ return taskDisplayArea != null && !taskDisplayArea.isRemoved();
+ }
+
+ /**
+ * Returns the root {@link TaskFragment}, which is usually also a {@link Task}.
+ */
+ @NonNull
+ TaskFragment getRootTaskFragment() {
+ final WindowContainer parent = getParent();
+ if (parent == null) return this;
+
+ final TaskFragment parentTaskFragment = parent.asTaskFragment();
+ return parentTaskFragment == null ? this : parentTaskFragment.getRootTaskFragment();
+ }
+
+ @Nullable
+ Task getRootTask() {
+ return getRootTaskFragment().asTask();
+ }
+
+ @Override
+ TaskFragment asTaskFragment() {
+ return this;
+ }
+
+ @Override
+ boolean isEmbedded() {
+ if (mIsEmbedded) {
+ return true;
+ }
+ final WindowContainer<?> parent = getParent();
+ if (parent != null) {
+ final TaskFragment taskFragment = parent.asTaskFragment();
+ return taskFragment != null && taskFragment.isEmbedded();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the TaskFragment that is being organized, which could be this or the ascendant
+ * TaskFragment.
+ */
+ @Nullable
+ TaskFragment getOrganizedTaskFragment() {
+ if (mTaskFragmentOrganizer != null) {
+ return this;
+ }
+
+ TaskFragment parentTaskFragment = getParent() != null ? getParent().asTaskFragment() : null;
+ return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null;
+ }
+
+ /**
+ * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}.
+ */
+ private void warnForNonLeafTaskFragment(String func) {
+ if (!isLeafTaskFragment()) {
+ Slog.w(TAG, func + " on non-leaf task fragment " + this);
+ }
+ }
+
+ boolean hasDirectChildActivities() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ if (mChildren.get(i).asActivityRecord() != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void cleanUpActivityReferences(@NonNull ActivityRecord r) {
+ if (mPausingActivity != null && mPausingActivity == r) {
+ mPausingActivity = null;
+ }
+
+ if (mResumedActivity != null && mResumedActivity == r) {
+ setResumedActivity(null, "cleanUpActivityReferences");
+ }
+ r.removeTimeouts();
+ }
+
+ /**
+ * Returns whether this TaskFragment is currently forced to be hidden for any reason.
+ */
+ protected boolean isForceHidden() {
+ return false;
+ }
+
+ boolean isLeafTaskFragment() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ if (mChildren.get(i).asTaskFragment() != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * This should be called when an child activity changes state. This should only
+ * be called from
+ * {@link ActivityRecord#setState(ActivityRecord.State, String)} .
+ * @param record The {@link ActivityRecord} whose state has changed.
+ * @param state The new state.
+ * @param reason The reason for the change.
+ */
+ void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state,
+ String reason) {
+ warnForNonLeafTaskFragment("onActivityStateChanged");
+ if (record == mResumedActivity && state != RESUMED) {
+ setResumedActivity(null, reason + " - onActivityStateChanged");
+ }
+
+ if (state == RESUMED) {
+ if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+ Slog.v(TAG, "set resumed activity to:" + record + " reason:" + reason);
+ }
+ setResumedActivity(record, reason + " - onActivityStateChanged");
+ if (record == mRootWindowContainer.getTopResumedActivity()) {
+ mAtmService.setResumedActivityUncheckLocked(record, reason);
+ }
+ mTaskSupervisor.mRecentTasks.add(record.getTask());
+ }
+ }
+
+ /**
+ * Resets local parameters because an app's activity died.
+ * @param app The app of the activity that died.
+ * @return {@code true} if the process of the pausing activity is died.
+ */
+ boolean handleAppDied(WindowProcessController app) {
+ warnForNonLeafTaskFragment("handleAppDied");
+ boolean isPausingDied = false;
+ if (mPausingActivity != null && mPausingActivity.app == app) {
+ ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s",
+ mPausingActivity);
+ mPausingActivity = null;
+ isPausingDied = true;
+ }
+ if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
+ mLastPausedActivity = null;
+ }
+ return isPausingDied;
+ }
+
+ void awakeFromSleeping() {
+ if (mPausingActivity != null) {
+ Slog.d(TAG, "awakeFromSleeping: previously pausing activity didn't pause");
+ mPausingActivity.activityPaused(true);
+ }
+ }
+
+ /**
+ * Tries to put the activities in the task fragment to sleep.
+ *
+ * If the task fragment is not in a state where its activities can be put to sleep, this
+ * function will start any necessary actions to move the task fragment into such a state.
+ * It is expected that this function get called again when those actions complete.
+ *
+ * @param shuttingDown {@code true} when the called because the device is shutting down.
+ * @return true if the root task finished going to sleep, false if the root task only started
+ * the process of going to sleep (checkReadyForSleep will be called when that process finishes).
+ */
+ boolean sleepIfPossible(boolean shuttingDown) {
+ boolean shouldSleep = true;
+ if (mResumedActivity != null) {
+ // Still have something resumed; can't sleep until it is paused.
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
+ startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
+ "sleep");
+ shouldSleep = false;
+ } else if (mPausingActivity != null) {
+ // Still waiting for something to pause; can't sleep yet.
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep still waiting to pause %s", mPausingActivity);
+ shouldSleep = false;
+ }
+
+ if (!shuttingDown) {
+ if (containsStoppingActivity()) {
+ // Still need to tell some activities to stop; can't sleep yet.
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep still need to stop %d activities",
+ mTaskSupervisor.mStoppingActivities.size());
+
+ mTaskSupervisor.scheduleIdle();
+ shouldSleep = false;
+ }
+ }
+
+ if (shouldSleep) {
+ updateActivityVisibilities(null /* starting */, 0 /* configChanges */,
+ !PRESERVE_WINDOWS, true /* notifyClients */);
+ }
+
+ return shouldSleep;
+ }
+
+ private boolean containsStoppingActivity() {
+ for (int i = mTaskSupervisor.mStoppingActivities.size() - 1; i >= 0; --i) {
+ ActivityRecord r = mTaskSupervisor.mStoppingActivities.get(i);
+ if (r.getTaskFragment() == this) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the TaskFragment is translucent and can have other contents visible behind
+ * it if needed. A TaskFragment is considered translucent if it don't contain a visible or
+ * starting (about to be visible) activity that is fullscreen (opaque).
+ * @param starting The currently starting activity or null if there is none.
+ */
+ @VisibleForTesting
+ boolean isTranslucent(ActivityRecord starting) {
+ if (!isAttached() || isForceHidden()) {
+ return true;
+ }
+ final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity,
+ PooledLambda.__(ActivityRecord.class), starting);
+ final ActivityRecord opaque = getActivity(p);
+ p.recycle();
+ return opaque == null;
+ }
+
+ private static boolean isOpaqueActivity(ActivityRecord r, ActivityRecord starting) {
+ if (r.finishing) {
+ // We don't factor in finishing activities when determining translucency since
+ // they will be gone soon.
+ return false;
+ }
+
+ if (!r.visibleIgnoringKeyguard && r != starting) {
+ // Also ignore invisible activities that are not the currently starting
+ // activity (about to be visible).
+ return false;
+ }
+
+ if (r.occludesParent()) {
+ // Root task isn't translucent if it has at least one fullscreen activity
+ // that is visible.
+ return true;
+ }
+ return false;
+ }
+
+ ActivityRecord getTopNonFinishingActivity() {
+ return getTopNonFinishingActivity(true /* includeOverlays */);
+ }
+
+ ActivityRecord getTopNonFinishingActivity(boolean includeOverlays) {
+ return getTopNonFinishingActivity(includeOverlays, true /* includingEmbeddedTask */);
+ }
+
+ /**
+ * Returns the top-most non-finishing activity, even if the activity is NOT ok to show to
+ * the current user.
+ * @param includeOverlays whether the task overlay activity should be included.
+ * @param includingEmbeddedTask whether the activity in a task that being embedded from this
+ * one should be included.
+ * @see #topRunningActivity(boolean, boolean)
+ */
+ ActivityRecord getTopNonFinishingActivity(boolean includeOverlays,
+ boolean includingEmbeddedTask) {
+ // Split into 4 to avoid object creation due to variable capture.
+ if (includeOverlays) {
+ if (includingEmbeddedTask) {
+ return getActivity((r) -> !r.finishing);
+ }
+ return getActivity((r) -> !r.finishing && r.getTask() == this.getTask());
+ }
+
+ if (includingEmbeddedTask) {
+ return getActivity((r) -> !r.finishing && !r.isTaskOverlay());
+ }
+ return getActivity(
+ (r) -> !r.finishing && !r.isTaskOverlay() && r.getTask() == this.getTask());
+ }
+
+ ActivityRecord topRunningActivity() {
+ return topRunningActivity(false /* focusableOnly */);
+ }
+
+ ActivityRecord topRunningActivity(boolean focusableOnly) {
+ return topRunningActivity(focusableOnly, true /* includingEmbeddedTask */);
+ }
+
+ /**
+ * Returns the top-most running activity, which the activity is non-finishing and ok to show
+ * to the current user.
+ *
+ * @see ActivityRecord#canBeTopRunning()
+ */
+ ActivityRecord topRunningActivity(boolean focusableOnly, boolean includingEmbeddedTask) {
+ // Split into 4 to avoid object creation due to variable capture.
+ if (focusableOnly) {
+ if (includingEmbeddedTask) {
+ return getActivity((r) -> r.canBeTopRunning() && r.isFocusable());
+ }
+ return getActivity(
+ (r) -> r.canBeTopRunning() && r.isFocusable() && r.getTask() == this.getTask());
+ }
+
+ if (includingEmbeddedTask) {
+ return getActivity(ActivityRecord::canBeTopRunning);
+ }
+ return getActivity((r) -> r.canBeTopRunning() && r.getTask() == this.getTask());
+ }
+
+ boolean isTopActivityFocusable() {
+ final ActivityRecord r = topRunningActivity();
+ return r != null ? r.isFocusable()
+ : (isFocusable() && getWindowConfiguration().canReceiveKeys());
+ }
+
+ /**
+ * Returns the visibility state of this TaskFragment.
+ *
+ * @param starting The currently starting activity or null if there is none.
+ */
+ @TaskFragmentVisibility
+ int getVisibility(ActivityRecord starting) {
+ if (!isAttached() || isForceHidden()) {
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+
+ if (isTopActivityLaunchedBehind()) {
+ return TASK_FRAGMENT_VISIBILITY_VISIBLE;
+ }
+
+ boolean gotRootSplitScreenFragment = false;
+ boolean gotOpaqueSplitScreenPrimary = false;
+ boolean gotOpaqueSplitScreenSecondary = false;
+ boolean gotTranslucentFullscreen = false;
+ boolean gotTranslucentAdjacent = false;
+ boolean gotTranslucentSplitScreenPrimary = false;
+ boolean gotTranslucentSplitScreenSecondary = false;
+ boolean shouldBeVisible = true;
+
+ // This TaskFragment is only considered visible if all its parent TaskFragments are
+ // considered visible, so check the visibility of all ancestor TaskFragment first.
+ final WindowContainer parent = getParent();
+ if (parent.asTaskFragment() != null) {
+ final int parentVisibility = parent.asTaskFragment().getVisibility(starting);
+ if (parentVisibility == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
+ // Can't be visible if parent isn't visible
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ } else if (parentVisibility == TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
+ // Parent is behind a translucent container so the highest visibility this container
+ // can get is that.
+ gotTranslucentFullscreen = true;
+ }
+ }
+
+ final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
+ final int windowingMode = getWindowingMode();
+ final boolean isAssistantType = isActivityTypeAssistant();
+ for (int i = parent.getChildCount() - 1; i >= 0; --i) {
+ final WindowContainer other = parent.getChildAt(i);
+ if (other == null) continue;
+
+ final boolean hasRunningActivities = hasRunningActivity(other);
+ if (other == this) {
+ if (!adjacentTaskFragments.isEmpty() && !gotTranslucentAdjacent) {
+ // The z-order of this TaskFragment is in middle of two adjacent TaskFragments
+ // and it cannot be visible if the TaskFragment on top is not translucent and
+ // is fully occluding this one.
+ for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) {
+ final TaskFragment taskFragment = adjacentTaskFragments.get(j);
+ if (!taskFragment.isTranslucent(starting)
+ && taskFragment.getBounds().contains(this.getBounds())) {
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+ }
+ }
+ // Should be visible if there is no other fragment occluding it, unless it doesn't
+ // have any running activities, not starting one and not home stack.
+ shouldBeVisible = hasRunningActivities
+ || (starting != null && starting.isDescendantOf(this))
+ || isActivityTypeHome();
+ break;
+ }
+
+ if (!hasRunningActivities) {
+ continue;
+ }
+
+ final int otherWindowingMode = other.getWindowingMode();
+ if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (isTranslucent(other, starting)) {
+ // Can be visible behind a translucent fullscreen TaskFragment.
+ gotTranslucentFullscreen = true;
+ continue;
+ }
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ && other.matchParentBounds()) {
+ if (isTranslucent(other, starting)) {
+ // Can be visible behind a translucent TaskFragment.
+ gotTranslucentFullscreen = true;
+ continue;
+ }
+ // Multi-window TaskFragment that matches parent bounds would occlude other children
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && !gotOpaqueSplitScreenPrimary) {
+ gotRootSplitScreenFragment = true;
+ gotTranslucentSplitScreenPrimary = isTranslucent(other, starting);
+ gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary;
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+ && gotOpaqueSplitScreenPrimary) {
+ // Can't be visible behind another opaque TaskFragment in split-screen-primary.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+ } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && !gotOpaqueSplitScreenSecondary) {
+ gotRootSplitScreenFragment = true;
+ gotTranslucentSplitScreenSecondary = isTranslucent(other, starting);
+ gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary;
+ if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+ && gotOpaqueSplitScreenSecondary) {
+ // Can't be visible behind another opaque TaskFragment in split-screen-secondary
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+ }
+ if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
+ // Can not be visible if we are in split-screen windowing mode and both halves of
+ // the screen are opaque.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+ if (isAssistantType && gotRootSplitScreenFragment) {
+ // Assistant TaskFragment can't be visible behind split-screen. In addition to
+ // this not making sense, it also works around an issue here we boost the z-order
+ // of the assistant window surfaces in window manager whenever it is visible.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+
+ final TaskFragment otherTaskFrag = other.asTaskFragment();
+ if (otherTaskFrag != null && otherTaskFrag.mAdjacentTaskFragment != null) {
+ if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
+ if (otherTaskFrag.isTranslucent(starting)
+ || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) {
+ // Can be visible behind a translucent adjacent TaskFragments.
+ gotTranslucentFullscreen = true;
+ gotTranslucentAdjacent = true;
+ continue;
+ }
+ // Can not be visible behind adjacent TaskFragments.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ } else {
+ adjacentTaskFragments.add(otherTaskFrag);
+ }
+ }
+
+ }
+
+ if (!shouldBeVisible) {
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+
+ // Handle cases when there can be a translucent split-screen TaskFragment on top.
+ switch (windowingMode) {
+ case WINDOWING_MODE_FULLSCREEN:
+ if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
+ // At least one of the split-screen TaskFragment that covers this one is
+ // translucent.
+ // When in split mode, home will be reparented to the secondary split while
+ // leaving TaskFragments not supporting split below. Due to
+ // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to
+ // the bottom, this makes sure TaskFragments not in split roots won't occlude
+ // home task unexpectedly.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+ break;
+ case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+ if (gotTranslucentSplitScreenPrimary) {
+ // Covered by translucent primary split-screen on top.
+ return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+ }
+ break;
+ case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+ if (gotTranslucentSplitScreenSecondary) {
+ // Covered by translucent secondary split-screen on top.
+ return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+ }
+ break;
+ }
+
+ // Lastly - check if there is a translucent fullscreen TaskFragment on top.
+ return gotTranslucentFullscreen
+ ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
+ : TASK_FRAGMENT_VISIBILITY_VISIBLE;
+ }
+
+ private static boolean hasRunningActivity(WindowContainer wc) {
+ if (wc.asTaskFragment() != null) {
+ return wc.asTaskFragment().topRunningActivity() != null;
+ }
+ return wc.asActivityRecord() != null && !wc.asActivityRecord().finishing;
+ }
+
+ private static boolean isTranslucent(WindowContainer wc, ActivityRecord starting) {
+ if (wc.asTaskFragment() != null) {
+ return wc.asTaskFragment().isTranslucent(starting);
+ } else if (wc.asActivityRecord() != null) {
+ return !wc.asActivityRecord().occludesParent();
+ }
+ return false;
+ }
+
+
+ private boolean isTopActivityLaunchedBehind() {
+ final ActivityRecord top = topRunningActivity();
+ return top != null && top.mLaunchTaskBehind;
+ }
+
+ final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges,
+ boolean preserveWindows, boolean notifyClients) {
+ mTaskSupervisor.beginActivityVisibilityUpdate();
+ try {
+ mEnsureActivitiesVisibleHelper.process(
+ starting, configChanges, preserveWindows, notifyClients);
+ } finally {
+ mTaskSupervisor.endActivityVisibilityUpdate();
+ }
+ }
+
+ final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
+ boolean deferPause) {
+ ActivityRecord next = topRunningActivity(true /* focusableOnly */);
+ if (next == null || !next.canResumeByCompat()) {
+ return false;
+ }
+
+ next.delayedResume = false;
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
+
+ // If the top activity is the resumed one, nothing to do.
+ if (mResumedActivity == next && next.isState(RESUMED)
+ && taskDisplayArea.allResumedActivitiesComplete()) {
+ // Make sure we have executed any pending transitions, since there
+ // should be nothing left to do at this point.
+ executeAppTransition(options);
+ // For devices that are not in fullscreen mode (e.g. freeform windows), it's possible
+ // we still want to check if the visibility of other windows have changed (e.g. bringing
+ // a fullscreen window forward to cover another freeform activity.)
+ if (taskDisplayArea.inMultiWindowMode()) {
+ taskDisplayArea.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ false /* preserveWindows */, true /* notifyClients */);
+ }
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
+ + "resumed %s", next);
+ return false;
+ }
+
+ // If we are currently pausing an activity, then don't do anything until that is done.
+ final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
+ if (!allPausedComplete) {
+ ProtoLog.v(WM_DEBUG_STATES,
+ "resumeTopActivity: Skip resume: some activity pausing.");
+ return false;
+ }
+
+ // If we are sleeping, and there is no resumed activity, and the top activity is paused,
+ // well that is the state we want.
+ if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
+ // Make sure we have executed any pending transitions, since there
+ // should be nothing left to do at this point.
+ executeAppTransition(options);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Going to sleep and"
+ + " all paused");
+ return false;
+ }
+
+ // Make sure that the user who owns this activity is started. If not,
+ // we will just leave it as is because someone should be bringing
+ // another user's activities to the top of the stack.
+ if (!mAtmService.mAmInternal.hasStartedUserState(next.mUserId)) {
+ Slog.w(TAG, "Skipping resume of top activity " + next
+ + ": user " + next.mUserId + " is stopped");
+ return false;
+ }
+
+ // The activity may be waiting for stop, but that is no longer
+ // appropriate for it.
+ mTaskSupervisor.mStoppingActivities.remove(next);
+
+ if (!next.translucentWindowLaunch)
+ next.launching = true;
+
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resuming " + next);
+
+ // TODO(b/223439401) adjust the value-add
+ // //Trigger Activity Resume
+ // if (mActivityTrigger != null) {
+ // mActivityTrigger.activityResumeTrigger(next.intent, next.info,
+ // next.info.applicationInfo,
+ // next.occludesParent());
+ // }
+
+ // if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) {
+ // mActivityPluginDelegate.activityInvokeNotification
+ // (next.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+ // }
+
+ mTaskSupervisor.setLaunchSource(next.info.applicationInfo.uid);
+
+ ActivityRecord lastResumed = null;
+ final Task lastFocusedRootTask = taskDisplayArea.getLastFocusedRootTask();
+ if (lastFocusedRootTask != null && lastFocusedRootTask != getRootTaskFragment().asTask()) {
+ // So, why aren't we using prev here??? See the param comment on the method. prev
+ // doesn't represent the last resumed activity. However, the last focus stack does if
+ // it isn't null.
+ lastResumed = lastFocusedRootTask.getTopResumedActivity();
+ }
+
+ boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
+ if (mResumedActivity != null) {
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
+ pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
+ next, "resumeTopActivity");
+ }
+ if (pausing) {
+ ProtoLog.v(WM_DEBUG_STATES, "resumeTopActivity: Skip resume: need to"
+ + " start pausing");
+ // At this point we want to put the upcoming activity's process
+ // at the top of the LRU list, since we know we will be needing it
+ // very soon and it would be a waste to let it get killed if it
+ // happens to be sitting towards the end.
+ if (next.attachedToProcess()) {
+ next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
+ true /* activityChange */, false /* updateOomAdj */,
+ false /* addPendingTopUid */);
+ } else if (!next.isProcessRunning()) {
+ // Since the start-process is asynchronous, if we already know the process of next
+ // activity isn't running, we can start the process earlier to save the time to wait
+ // for the current activity to be paused.
+ final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
+ mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
+ isTop ? "pre-top-activity" : "pre-activity");
+ }
+ if (lastResumed != null) {
+ lastResumed.setWillCloseOrEnterPip(true);
+ }
+ return true;
+ } else if (mResumedActivity == next && next.isState(RESUMED)
+ && taskDisplayArea.allResumedActivitiesComplete()) {
+ // It is possible for the activity to be resumed when we paused back stacks above if the
+ // next activity doesn't have to wait for pause to complete.
+ // So, nothing else to-do except:
+ // Make sure we have executed any pending transitions, since there
+ // should be nothing left to do at this point.
+ executeAppTransition(options);
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity resumed "
+ + "(dontWaitForPause) %s", next);
+ return true;
+ }
+
+ // If the most recent activity was noHistory but was only stopped rather
+ // than stopped+finished because the device went to sleep, we need to make
+ // sure to finish it as we're making a new activity topmost.
+ if (shouldSleepActivities()) {
+ mTaskSupervisor.finishNoHistoryActivitiesIfNeeded(next);
+ }
+
+ if (prev != null && prev != next && next.nowVisible) {
+ // The next activity is already visible, so hide the previous
+ // activity's windows right now so we can show the new one ASAP.
+ // We only do this if the previous is finishing, which should mean
+ // it is on top of the one being resumed so hiding it quickly
+ // is good. Otherwise, we want to do the normal route of allowing
+ // the resumed activity to be shown so we can decide if the
+ // previous should actually be hidden depending on whether the
+ // new one is found to be full-screen or not.
+ if (prev.finishing) {
+ prev.setVisibility(false);
+ if (DEBUG_SWITCH) {
+ Slog.v(TAG_SWITCH, "Not waiting for visible to hide: " + prev
+ + ", nowVisible=" + next.nowVisible);
+ }
+ } else {
+ if (DEBUG_SWITCH) {
+ Slog.v(TAG_SWITCH, "Previous already visible but still waiting to hide: " + prev
+ + ", nowVisible=" + next.nowVisible);
+ }
+ }
+ }
+
+ // Launching this app's activity, make sure the app is no longer
+ // considered stopped.
+ try {
+ mTaskSupervisor.getActivityMetricsLogger()
+ .notifyBeforePackageUnstopped(next.packageName);
+ mAtmService.getPackageManager().setPackageStoppedState(
+ next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
+ } catch (RemoteException e1) {
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed trying to unstop package "
+ + next.packageName + ": " + e);
+ }
+
+ // We are starting up the next activity, so tell the window manager
+ // that the previous one will be hidden soon. This way it can know
+ // to ignore it when computing the desired screen orientation.
+ boolean anim = true;
+ final DisplayContent dc = taskDisplayArea.mDisplayContent;
+ // TODO(b/223439401) adjust the value-add
+ // if (mPerf == null) {
+ // mPerf = new BoostFramework();
+ // }
+ if (prev != null) {
+ if (prev.finishing) {
+ if (DEBUG_TRANSITION) {
+ Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev);
+ }
+ if (mTaskSupervisor.mNoAnimActivities.contains(prev)) {
+ anim = false;
+ dc.prepareAppTransition(TRANSIT_NONE);
+ } else {
+ // TODO(b/223439401) adjust the value-add
+ // if(prev.getTask() != next.getTask() && mPerf != null) {
+ // mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST,
+ // next.packageName);
+ // }
+ dc.prepareAppTransition(TRANSIT_CLOSE);
+ }
+ prev.setVisibility(false);
+ } else {
+ if (DEBUG_TRANSITION) {
+ Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev);
+ }
+ if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+ anim = false;
+ dc.prepareAppTransition(TRANSIT_NONE);
+ } else {
+ // TODO(b/223439401) adjust the value-add
+ // if(prev.getTask() != next.getTask() && mPerf != null) {
+ // mPerf.perfHint(BoostFramework.VENDOR_HINT_ANIM_BOOST,
+ // next.packageName);
+ // }
+ dc.prepareAppTransition(TRANSIT_OPEN,
+ next.mLaunchTaskBehind ? TRANSIT_FLAG_OPEN_BEHIND : 0);
+ }
+ }
+ } else {
+ if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous");
+ if (mTaskSupervisor.mNoAnimActivities.contains(next)) {
+ anim = false;
+ dc.prepareAppTransition(TRANSIT_NONE);
+ } else {
+ dc.prepareAppTransition(TRANSIT_OPEN);
+ }
+ }
+
+ if (anim) {
+ next.applyOptionsAnimation();
+ } else {
+ next.abortAndClearOptionsAnimation();
+ }
+
+ mTaskSupervisor.mNoAnimActivities.clear();
+
+ if (next.attachedToProcess()) {
+ if (DEBUG_SWITCH) {
+ Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
+ + " visibleRequested=" + next.mVisibleRequested);
+ }
+
+ // If the previous activity is translucent, force a visibility update of
+ // the next activity, so that it's added to WM's opening app list, and
+ // transition animation can be set up properly.
+ // For example, pressing Home button with a translucent activity in focus.
+ // Launcher is already visible in this case. If we don't add it to opening
+ // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
+ // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
+ final boolean lastActivityTranslucent = inMultiWindowMode()
+ || mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
+
+ // This activity is now becoming visible.
+ if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+ next.setVisibility(true);
+ }
+
+ // schedule launch ticks to collect information about slow apps.
+ next.startLaunchTickingLocked();
+
+ ActivityRecord lastResumedActivity =
+ lastFocusedRootTask == null ? null
+ : lastFocusedRootTask.getTopResumedActivity();
+ final ActivityRecord.State lastState = next.getState();
+
+ mAtmService.updateCpuStats();
+
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (in existing)", next);
+
+ next.setState(RESUMED, "resumeTopActivity");
+
+ // Have the window manager re-evaluate the orientation of
+ // the screen based on the new activity order.
+ boolean notUpdated = true;
+
+ // Activity should also be visible if set mLaunchTaskBehind to true (see
+ // ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
+ if (shouldBeVisible(next)) {
+ // We have special rotation behavior when here is some active activity that
+ // requests specific orientation or Keyguard is locked. Make sure all activity
+ // visibilities are set correctly as well as the transition is updated if needed
+ // to get the correct rotation behavior. Otherwise the following call to update
+ // the orientation may cause incorrect configurations delivered to client as a
+ // result of invisible window resize.
+ // TODO: Remove this once visibilities are set correctly immediately when
+ // starting an activity.
+ notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ true /* markFrozenIfConfigChanged */, false /* deferResume */);
+ }
+
+ if (notUpdated) {
+ // The configuration update wasn't able to keep the existing
+ // instance of the activity, and instead started a new one.
+ // We should be all done, but let's just make sure our activity
+ // is still at the top and schedule another run if something
+ // weird happened.
+ ActivityRecord nextNext = topRunningActivity();
+ ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
+ + "%s, new next: %s", next, nextNext);
+ if (nextNext != next) {
+ // Do over!
+ mTaskSupervisor.scheduleResumeTopActivities();
+ }
+ if (!next.mVisibleRequested || next.stopped) {
+ next.setVisibility(true);
+ }
+ next.completeResumeLocked();
+ return true;
+ }
+
+ try {
+ final ClientTransaction transaction =
+ ClientTransaction.obtain(next.app.getThread(), next.appToken);
+ // Deliver all pending results.
+ ArrayList<ResultInfo> a = next.results;
+ if (a != null) {
+ final int size = a.size();
+ if (!next.finishing && size > 0) {
+ if (DEBUG_RESULTS) {
+ Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
+ }
+ transaction.addCallback(ActivityResultItem.obtain(a));
+ }
+ }
+
+ if (next.newIntents != null) {
+ transaction.addCallback(
+ NewIntentItem.obtain(next.newIntents, true /* resume */));
+ }
+
+ // Well the app will no longer be stopped.
+ // Clear app token stopped state in window manager if needed.
+ next.notifyAppResumed(next.stopped);
+
+ EventLogTags.writeWmResumeActivity(next.mUserId, System.identityHashCode(next),
+ next.getTask().mTaskId, next.shortComponentName);
+
+ mAtmService.getAppWarningsLocked().onResumeActivity(next);
+ next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
+ next.abortAndClearOptionsAnimation();
+ transaction.setLifecycleStateRequest(
+ ResumeActivityItem.obtain(next.app.getReportedProcState(),
+ dc.isNextTransitionForward()));
+ mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
+ } catch (Exception e) {
+ // Whoops, need to restart this activity!
+ ProtoLog.v(WM_DEBUG_STATES, "Resume failed; resetting state to %s: "
+ + "%s", lastState, next);
+ next.setState(lastState, "resumeTopActivityInnerLocked");
+
+ // lastResumedActivity being non-null implies there is a lastStack present.
+ if (lastResumedActivity != null) {
+ lastResumedActivity.setState(RESUMED, "resumeTopActivityInnerLocked");
+ }
+
+ Slog.i(TAG, "Restarting because process died: " + next);
+ if (!next.hasBeenLaunched) {
+ next.hasBeenLaunched = true;
+ } else if (SHOW_APP_STARTING_PREVIEW && lastFocusedRootTask != null
+ && lastFocusedRootTask.isTopRootTaskInDisplayArea()) {
+ next.showStartingWindow(false /* taskSwitch */);
+ }
+ mTaskSupervisor.startSpecificActivity(next, true, false);
+ return true;
+ }
+
+ // From this point on, if something goes wrong there is no way
+ // to recover the activity.
+ try {
+ next.completeResumeLocked();
+ } catch (Exception e) {
+ // If any exception gets thrown, toss away this
+ // activity and try the next one.
+ Slog.w(TAG, "Exception thrown during resume of " + next, e);
+ next.finishIfPossible("resume-exception", true /* oomAdj */);
+ return true;
+ }
+ } else {
+ // Whoops, need to restart this activity!
+ if (!next.hasBeenLaunched) {
+ next.hasBeenLaunched = true;
+ } else {
+ if (SHOW_APP_STARTING_PREVIEW) {
+ next.showStartingWindow(false /* taskSwich */);
+ }
+ if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
+ }
+ ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next);
+ mTaskSupervisor.startSpecificActivity(next, true, true);
+ }
+
+ return true;
+ }
+
+ boolean shouldSleepOrShutDownActivities() {
+ return shouldSleepActivities() || mAtmService.mShuttingDown;
+ }
+
+ /**
+ * Returns true if the TaskFragment should be visible.
+ *
+ * @param starting The currently starting activity or null if there is none.
+ */
+ boolean shouldBeVisible(ActivityRecord starting) {
+ return getVisibility(starting) != TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+
+ /**
+ * Returns {@code true} is the activity in this TaskFragment can be resumed.
+ *
+ * @param starting The currently starting activity or {@code null} if there is none.
+ */
+ boolean canBeResumed(@Nullable ActivityRecord starting) {
+ // No need to resume activity in TaskFragment that is not visible.
+ return isTopActivityFocusable()
+ && getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE;
+ }
+
+ boolean isFocusableAndVisible() {
+ return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
+ }
+
+ final boolean startPausing(boolean uiSleeping, ActivityRecord resuming, String reason) {
+ return startPausing(mTaskSupervisor.mUserLeaving, uiSleeping, resuming, reason);
+ }
+
+ /**
+ * Start pausing the currently resumed activity. It is an error to call this if there
+ * is already an activity being paused or there is no resumed activity.
+ *
+ * @param userLeaving True if this should result in an onUserLeaving to the current activity.
+ * @param uiSleeping True if this is happening with the user interface going to sleep (the
+ * screen turning off).
+ * @param resuming The activity we are currently trying to resume or null if this is not being
+ * called as part of resuming the top activity, so we shouldn't try to instigate
+ * a resume here if not null.
+ * @param reason The reason of pausing the activity.
+ * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
+ * it to tell us when it is done.
+ */
+ boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
+ String reason) {
+ if (!hasDirectChildActivities()) {
+ return false;
+ }
+
+ ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
+ mResumedActivity);
+
+ if (mPausingActivity != null) {
+ Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
+ + " state=" + mPausingActivity.getState());
+ if (!shouldSleepActivities()) {
+ // Avoid recursion among check for sleep and complete pause during sleeping.
+ // Because activity will be paused immediately after resume, just let pause
+ // be completed by the order of activity paused from clients.
+ completePause(false, resuming);
+ }
+ }
+ ActivityRecord prev = mResumedActivity;
+
+ if (prev == null) {
+ if (resuming == null) {
+ Slog.wtf(TAG, "Trying to pause when nothing is resumed");
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
+ return false;
+ }
+
+ if (prev == resuming) {
+ Slog.wtf(TAG, "Trying to pause activity that is in process of being resumed");
+ return false;
+ }
+
+ // TODO(b/223439401) adjust the value-add
+ // //Trigger Activity Pause
+ // if (mActivityTrigger != null) {
+ // mActivityTrigger.activityPauseTrigger(prev.intent, prev.info,
+ // prev.info.applicationInfo);
+ // }
+
+ // if (mActivityPluginDelegate != null && getWindowingMode() != WINDOWING_MODE_UNDEFINED) {
+ // mActivityPluginDelegate.activitySuspendNotification
+ // (prev.info.packageName, getWindowingMode() == WINDOWING_MODE_FULLSCREEN, true);
+ // }
+ ProtoLog.v(WM_DEBUG_STATES, "Moving to PAUSING: %s", prev);
+ mPausingActivity = prev;
+ mLastPausedActivity = prev;
+ if (!prev.finishing && prev.isNoHistory()
+ && !mTaskSupervisor.mNoHistoryActivities.contains(prev)) {
+ mTaskSupervisor.mNoHistoryActivities.add(prev);
+ }
+ prev.setState(PAUSING, "startPausingLocked");
+ prev.getTask().touchActiveTime();
+
+ mAtmService.updateCpuStats();
+
+ boolean pauseImmediately = false;
+ boolean shouldAutoPip = false;
+ if (resuming != null) {
+ // Resuming the new resume activity only if the previous activity can't go into Pip
+ // since we want to give Pip activities a chance to enter Pip before resuming the
+ // next activity.
+ final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
+ "shouldAutoPipWhilePausing", userLeaving);
+ if (lastResumedCanPip && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
+ shouldAutoPip = true;
+ } else if (!lastResumedCanPip) {
+ // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
+ // activity to be paused.
+ pauseImmediately = (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
+ } else {
+ // The previous activity may still enter PIP even though it did not allow auto-PIP.
+ }
+ }
+
+ if (prev.attachedToProcess()) {
+ if (shouldAutoPip) {
+ boolean didAutoPip = mAtmService.enterPictureInPictureMode(
+ prev, prev.pictureInPictureArgs);
+ ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
+ + "directly: %s, didAutoPip: %b", prev, didAutoPip);
+ } else {
+ schedulePauseActivity(prev, userLeaving, pauseImmediately, reason);
+ }
+ } else {
+ mPausingActivity = null;
+ mLastPausedActivity = null;
+ mTaskSupervisor.mNoHistoryActivities.remove(prev);
+ }
+
+ // If we are not going to sleep, we want to ensure the device is
+ // awake until the next activity is started.
+ if (!uiSleeping && !mAtmService.isSleepingOrShuttingDownLocked()) {
+ mTaskSupervisor.acquireLaunchWakelock();
+ }
+
+ // If already entered PIP mode, no need to keep pausing.
+ if (mPausingActivity != null) {
+ // Have the window manager pause its key dispatching until the new
+ // activity has started. If we're pausing the activity just because
+ // the screen is being turned off and the UI is sleeping, don't interrupt
+ // key dispatch; the same activity will pick it up again on wakeup.
+ if (!uiSleeping) {
+ prev.pauseKeyDispatchingLocked();
+ } else {
+ ProtoLog.v(WM_DEBUG_STATES, "Key dispatch not paused for screen off");
+ }
+
+ if (pauseImmediately) {
+ // If the caller said they don't want to wait for the pause, then complete
+ // the pause now.
+ completePause(false, resuming);
+ return false;
+
+ } else {
+ prev.schedulePauseTimeout();
+ // Unset readiness since we now need to wait until this pause is complete.
+ mTransitionController.setReady(this, false /* ready */);
+ return true;
+ }
+
+ } else {
+ // This activity either failed to schedule the pause or it entered PIP mode,
+ // so just treat it as being paused now.
+ ProtoLog.v(WM_DEBUG_STATES, "Activity not running or entered PiP, resuming next.");
+ if (resuming == null) {
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
+ return false;
+ }
+ }
+
+ void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
+ boolean pauseImmediately, String reason) {
+ ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
+ try {
+ EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
+ prev.shortComponentName, "userLeaving=" + userLeaving, reason);
+
+ mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
+ prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving,
+ prev.configChangeFlags, pauseImmediately));
+ } catch (Exception e) {
+ // Ignore exception, if process died other code will cleanup.
+ Slog.w(TAG, "Exception thrown during pause", e);
+ mPausingActivity = null;
+ mLastPausedActivity = null;
+ mTaskSupervisor.mNoHistoryActivities.remove(prev);
+ }
+ }
+
+ @VisibleForTesting
+ void completePause(boolean resumeNext, ActivityRecord resuming) {
+ // Complete the pausing process of a pausing activity, so it doesn't make sense to
+ // operate on non-leaf tasks.
+ // warnForNonLeafTask("completePauseLocked");
+
+ ActivityRecord prev = mPausingActivity;
+ ProtoLog.v(WM_DEBUG_STATES, "Complete pause: %s", prev);
+
+ if (prev != null) {
+ prev.setWillCloseOrEnterPip(false);
+ final boolean wasStopping = prev.isState(STOPPING);
+ prev.setState(PAUSED, "completePausedLocked");
+ if (prev.finishing) {
+ // We will update the activity visibility later, no need to do in
+ // completeFinishing(). Updating visibility here might also making the next
+ // activities to be resumed, and could result in wrong app transition due to
+ // lack of previous activity information.
+ ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
+ prev = prev.completeFinishing(false /* updateVisibility */,
+ "completePausedLocked");
+ } else if (prev.hasProcess()) {
+ ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ + "wasStopping=%b visibleRequested=%b", prev, wasStopping,
+ prev.mVisibleRequested);
+ if (prev.deferRelaunchUntilPaused) {
+ // Complete the deferred relaunch that was waiting for pause to complete.
+ ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
+ prev.relaunchActivityLocked(prev.preserveWindowOnDeferredRelaunch);
+ } else if (wasStopping) {
+ // We are also stopping, the stop request must have gone soon after the pause.
+ // We can't clobber it, because the stop confirmation will not be handled.
+ // We don't need to schedule another stop, we only need to let it happen.
+ prev.setState(STOPPING, "completePausedLocked");
+ } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+ // Clear out any deferred client hide we might currently have.
+ prev.setDeferHidingClient(false);
+ // If we were visible then resumeTopActivities will release resources before
+ // stopping.
+ prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */,
+ "completePauseLocked");
+ }
+ } else {
+ ProtoLog.v(WM_DEBUG_STATES, "App died during pause, not stopping: %s", prev);
+ prev = null;
+ }
+ // It is possible the activity was freezing the screen before it was paused.
+ // In that case go ahead and remove the freeze this activity has on the screen
+ // since it is no longer visible.
+ if (prev != null) {
+ prev.stopFreezingScreenLocked(true /*force*/);
+ }
+ mPausingActivity = null;
+ }
+
+ if (resumeNext) {
+ final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
+ if (topRootTask != null && !topRootTask.shouldSleepOrShutDownActivities()) {
+ mRootWindowContainer.resumeFocusedTasksTopActivities(topRootTask, prev,
+ null /* targetOptions */);
+ } else {
+ // checkReadyForSleep();
+ final ActivityRecord top =
+ topRootTask != null ? topRootTask.topRunningActivity() : null;
+ if (top == null || (prev != null && top != prev)) {
+ // If there are no more activities available to run, do resume anyway to start
+ // something. Also if the top activity on the root task is not the just paused
+ // activity, we need to go ahead and resume it to ensure we complete an
+ // in-flight app switch.
+ mRootWindowContainer.resumeFocusedTasksTopActivities();
+ }
+ }
+ }
+
+ if (prev != null) {
+ prev.resumeKeyDispatchingLocked();
+ }
+
+ mRootWindowContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS);
+
+ // Notify when the task stack has changed, but only if visibilities changed (not just
+ // focus). Also if there is an active root pinned task - we always want to notify it about
+ // task stack changes, because its positioning may depend on it.
+ if (mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause
+ || (getDisplayArea() != null && getDisplayArea().hasPinnedTask())) {
+ mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
+ mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
+ }
+ }
+
+ @Override
+ void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+ super.forAllTaskFragments(callback, traverseTopToBottom);
+ callback.accept(this);
+ }
+
+ @Override
+ void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+ final int count = mChildren.size();
+ boolean isLeafTaskFrag = true;
+ if (traverseTopToBottom) {
+ for (int i = count - 1; i >= 0; --i) {
+ final TaskFragment child = mChildren.get(i).asTaskFragment();
+ if (child != null) {
+ isLeafTaskFrag = false;
+ child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+ }
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ final TaskFragment child = mChildren.get(i).asTaskFragment();
+ if (child != null) {
+ isLeafTaskFrag = false;
+ child.forAllLeafTaskFragments(callback, traverseTopToBottom);
+ }
+ }
+ }
+ if (isLeafTaskFrag) callback.accept(this);
+ }
+
+ @Override
+ boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) {
+ boolean isLeafTaskFrag = true;
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final TaskFragment child = mChildren.get(i).asTaskFragment();
+ if (child != null) {
+ isLeafTaskFrag = false;
+ if (child.forAllLeafTaskFragments(callback)) {
+ return true;
+ }
+ }
+ }
+ if (isLeafTaskFrag) {
+ return callback.apply(this);
+ }
+ return false;
+ }
+
+ void addChild(ActivityRecord r) {
+ addChild(r, POSITION_TOP);
+ }
+
+ @Override
+ void addChild(WindowContainer child, int index) {
+ mClearedTaskForReuse = false;
+
+ boolean isAddingActivity = child.asActivityRecord() != null;
+ final Task task = isAddingActivity ? getTask() : null;
+
+ // If this task had any activity before we added this one.
+ boolean taskHadActivity = task != null && task.getActivity(Objects::nonNull) != null;
+ // getActivityType() looks at the top child, so we need to read the type before adding
+ // a new child in case the new child is on top and UNDEFINED.
+ final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+
+ super.addChild(child, index);
+
+ if (isAddingActivity && task != null) {
+ child.asActivityRecord().inHistory = true;
+ task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord());
+ }
+ }
+
+ @Override
+ void onChildPositionChanged(WindowContainer child) {
+ super.onChildPositionChanged(child);
+
+ sendTaskFragmentInfoChanged();
+ }
+
+ void executeAppTransition(ActivityOptions options) {
+ // No app transition applied to the task fragment.
+ }
+
+ @Override
+ RemoteAnimationTarget createRemoteAnimationTarget(
+ RemoteAnimationController.RemoteAnimationRecord record) {
+ final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
+ // There may be a trampoline activity without window on top of the existing task
+ // which is moving to front. Exclude the finishing activity so the window of next
+ // activity can be chosen to create the animation target.
+ ? getTopNonFinishingActivity()
+ : getTopMostActivity();
+ return activity != null ? activity.createRemoteAnimationTarget(record) : null;
+ }
+
+ @Override
+ boolean canCreateRemoteAnimationTarget() {
+ return true;
+ }
+
+ boolean shouldSleepActivities() {
+ return false;
+ }
+
+ @Override
+ void resolveOverrideConfiguration(Configuration newParentConfig) {
+ mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
+ super.resolveOverrideConfiguration(newParentConfig);
+
+ int windowingMode =
+ getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+ final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
+
+ // Resolve override windowing mode to fullscreen for home task (even on freeform
+ // display), or split-screen if in split-screen mode.
+ if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
+ windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
+ ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
+ getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+ }
+
+ // Do not allow tasks not support multi window to be in a multi-window mode, unless it is in
+ // pinned windowing mode.
+ if (!supportsMultiWindow()) {
+ final int candidateWindowingMode =
+ windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : parentWindowingMode;
+ if (WindowConfiguration.inMultiWindowMode(candidateWindowingMode)
+ && candidateWindowingMode != WINDOWING_MODE_PINNED) {
+ getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_FULLSCREEN);
+ }
+ }
+
+ final Task thisTask = asTask();
+ // Embedded Task's configuration should go with parent TaskFragment, so we don't re-compute
+ // configuration here.
+ if (thisTask != null && !thisTask.isEmbedded()) {
+ thisTask.resolveLeafTaskOnlyOverrideConfigs(newParentConfig,
+ mTmpBounds /* previousBounds */);
+ }
+ computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+ }
+
+ boolean supportsMultiWindow() {
+ return supportsMultiWindowInDisplayArea(getDisplayArea());
+ }
+
+ /**
+ * @return whether this task supports multi-window if it is in the given
+ * {@link TaskDisplayArea}.
+ */
+ boolean supportsMultiWindowInDisplayArea(@Nullable TaskDisplayArea tda) {
+ if (!mAtmService.mSupportsMultiWindow) {
+ return false;
+ }
+ final Task task = getTask();
+ if (task == null) {
+ return false;
+ }
+ if (tda == null) {
+ Slog.w(TAG, "Can't find TaskDisplayArea to determine support for multi"
+ + " window. Task id=" + getTaskId() + " attached=" + isAttached());
+ return false;
+ }
+ if (!getTask().isResizeable() && !tda.supportsNonResizableMultiWindow()) {
+ // Not support non-resizable in multi window.
+ return false;
+ }
+
+ final ActivityRecord rootActivity = getTask().getRootActivity();
+ return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight,
+ rootActivity != null ? rootActivity.info : null);
+ }
+
+ private int getTaskId() {
+ return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
+ }
+
+ /**
+ * Ensures all visible activities at or below the input activity have the right configuration.
+ */
+ void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
+ mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
+ }
+
+ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+ @NonNull Configuration parentConfig) {
+ computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
+ null /* compatInsets */);
+ }
+
+ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+ @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
+ if (overrideDisplayInfo != null) {
+ // Make sure the screen related configs can be computed by the provided display info.
+ inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
+ invalidateAppBoundsConfig(inOutConfig);
+ }
+ computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
+ null /* compatInsets */);
+ }
+
+ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+ @NonNull Configuration parentConfig,
+ @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+ if (compatInsets != null) {
+ // Make sure the app bounds can be computed by the compat insets.
+ invalidateAppBoundsConfig(inOutConfig);
+ }
+ computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
+ compatInsets);
+ }
+
+ /**
+ * Forces the app bounds related configuration can be computed by
+ * {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
+ * ActivityRecord.CompatDisplayInsets)}.
+ */
+ private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
+ final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (appBounds != null) {
+ appBounds.setEmpty();
+ }
+ inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ }
+
+ /**
+ * Calculates configuration values used by the client to get resources. This should be run
+ * using app-facing bounds (bounds unmodified by animations or transient interactions).
+ *
+ * This assumes bounds are non-empty/null. For the null-bounds case, the caller is likely
+ * configuring an "inherit-bounds" window which means that all configuration settings would
+ * just be inherited from the parent configuration.
+ **/
+ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
+ @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
+ @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
+ int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+ windowingMode = parentConfig.windowConfiguration.getWindowingMode();
+ }
+
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = parentConfig.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+
+ // The bounds may have been overridden at this level. If the parent cannot cover these
+ // bounds, the configuration is still computed according to the override bounds.
+ final boolean insideParentBounds;
+
+ final Rect parentBounds = parentConfig.windowConfiguration.getBounds();
+ final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
+ if (resolvedBounds == null || resolvedBounds.isEmpty()) {
+ mTmpFullBounds.set(parentBounds);
+ insideParentBounds = true;
+ } else {
+ mTmpFullBounds.set(resolvedBounds);
+ insideParentBounds = parentBounds.contains(resolvedBounds);
+ }
+
+ // Non-null compatibility insets means the activity prefers to keep its original size, so
+ // out bounds doesn't need to be restricted by the parent or current display
+ final boolean customContainerPolicy = compatInsets != null;
+
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ // App-bounds hasn't been overridden, so calculate a value for it.
+ inOutConfig.windowConfiguration.setAppBounds(mTmpFullBounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+
+ if (!customContainerPolicy && windowingMode != WINDOWING_MODE_FREEFORM) {
+ final Rect containingAppBounds;
+ if (insideParentBounds) {
+ containingAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ } else {
+ // Restrict appBounds to display non-decor rather than parent because the
+ // override bounds are beyond the parent. Otherwise, it won't match the
+ // overridden bounds.
+ final TaskDisplayArea displayArea = getDisplayArea();
+ containingAppBounds = displayArea != null
+ ? displayArea.getWindowConfiguration().getAppBounds() : null;
+ }
+ if (containingAppBounds != null && !containingAppBounds.isEmpty()) {
+ outAppBounds.intersect(containingAppBounds);
+ }
+ }
+ }
+
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED
+ || inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ if (!customContainerPolicy && WindowConfiguration.isFloating(windowingMode)) {
+ mTmpNonDecorBounds.set(mTmpFullBounds);
+ mTmpStableBounds.set(mTmpFullBounds);
+ } else if (!customContainerPolicy
+ && (overrideDisplayInfo != null || getDisplayContent() != null)) {
+ final DisplayInfo di = overrideDisplayInfo != null
+ ? overrideDisplayInfo
+ : getDisplayContent().getDisplayInfo();
+
+ // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
+ // area, i.e. the screen area without the system bars.
+ // The non decor inset are areas that could never be removed in Honeycomb. See
+ // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
+ } else {
+ // Apply the given non-decor and stable insets to calculate the corresponding bounds
+ // for screen size of configuration.
+ int rotation = inOutConfig.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED) {
+ rotation = parentConfig.windowConfiguration.getRotation();
+ }
+ if (rotation != ROTATION_UNDEFINED && customContainerPolicy) {
+ mTmpNonDecorBounds.set(mTmpFullBounds);
+ mTmpStableBounds.set(mTmpFullBounds);
+ compatInsets.getBoundsByRotation(mTmpBounds, rotation);
+ intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds,
+ compatInsets.mNonDecorInsets[rotation]);
+ intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds,
+ compatInsets.mStableInsets[rotation]);
+ outAppBounds.set(mTmpNonDecorBounds);
+ } else {
+ // Set to app bounds because it excludes decor insets.
+ mTmpNonDecorBounds.set(outAppBounds);
+ mTmpStableBounds.set(outAppBounds);
+ }
+ }
+
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
+ inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
+ ? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
+ : overrideScreenWidthDp;
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
+ inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
+ ? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
+ : overrideScreenHeightDp;
+ }
+
+ if (inOutConfig.smallestScreenWidthDp
+ == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ // When entering to or exiting from Pip, the PipTaskOrganizer will set the
+ // windowing mode of the activity in the task to WINDOWING_MODE_FULLSCREEN and
+ // temporarily set the bounds of the task to fullscreen size for transitioning.
+ // It will get the wrong value if the calculation is based on this temporary
+ // fullscreen bounds.
+ // We should just inherit the value from parent for this temporary state.
+ final boolean inPipTransition = windowingMode == WINDOWING_MODE_PINNED
+ && !mTmpFullBounds.isEmpty() && mTmpFullBounds.equals(parentBounds);
+ if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
+ // For floating tasks, calculate the smallest width from the bounds of the task
+ inOutConfig.smallestScreenWidthDp = (int) (
+ Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
+ }
+ // otherwise, it will just inherit
+ }
+ }
+
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
+ if (inOutConfig.screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
+ // For calculating screen layout, we need to use the non-decor inset screen area for the
+ // calculation for compatibility reasons, i.e. screen area without system bars that
+ // could never go away in Honeycomb.
+ int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
+ int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+ // Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
+ // undefined so it can't be used.
+ if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ compatScreenWidthDp = inOutConfig.screenWidthDp;
+ }
+ if (inOutConfig.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ compatScreenHeightDp = inOutConfig.screenHeightDp;
+ }
+ // Reducing the screen layout starting from its parent config.
+ inOutConfig.screenLayout = computeScreenLayoutOverride(parentConfig.screenLayout,
+ compatScreenWidthDp, compatScreenHeightDp);
+ }
+ }
+
+ /**
+ * Gets bounds with non-decor and stable insets applied respectively.
+ *
+ * If bounds overhangs the display, those edges will not get insets. See
+ * {@link #intersectWithInsetsIfFits}
+ *
+ * @param outNonDecorBounds where to place bounds with non-decor insets applied.
+ * @param outStableBounds where to place bounds with stable insets applied.
+ * @param bounds the bounds to inset.
+ */
+ void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
+ DisplayInfo displayInfo) {
+ outNonDecorBounds.set(bounds);
+ outStableBounds.set(bounds);
+ final Task rootTask = getRootTaskFragment().asTask();
+ if (rootTask == null || rootTask.mDisplayContent == null) {
+ return;
+ }
+ mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+
+ final DisplayPolicy policy = rootTask.mDisplayContent.getDisplayPolicy();
+ policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth,
+ displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets);
+ intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets);
+
+ policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation);
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
+ }
+
+ /**
+ * Intersects inOutBounds with intersectBounds-intersectInsets. If inOutBounds is larger than
+ * intersectBounds on a side, then the respective side will not be intersected.
+ *
+ * The assumption is that if inOutBounds is initially larger than intersectBounds, then the
+ * inset on that side is no-longer applicable. This scenario happens when a task's minimal
+ * bounds are larger than the provided parent/display bounds.
+ *
+ * @param inOutBounds the bounds to intersect.
+ * @param intersectBounds the bounds to intersect with.
+ * @param intersectInsets insets to apply to intersectBounds before intersecting.
+ */
+ static void intersectWithInsetsIfFits(
+ Rect inOutBounds, Rect intersectBounds, Rect intersectInsets) {
+ if (inOutBounds.right <= intersectBounds.right) {
+ inOutBounds.right =
+ Math.min(intersectBounds.right - intersectInsets.right, inOutBounds.right);
+ }
+ if (inOutBounds.bottom <= intersectBounds.bottom) {
+ inOutBounds.bottom =
+ Math.min(intersectBounds.bottom - intersectInsets.bottom, inOutBounds.bottom);
+ }
+ if (inOutBounds.left >= intersectBounds.left) {
+ inOutBounds.left =
+ Math.max(intersectBounds.left + intersectInsets.left, inOutBounds.left);
+ }
+ if (inOutBounds.top >= intersectBounds.top) {
+ inOutBounds.top =
+ Math.max(intersectBounds.top + intersectInsets.top, inOutBounds.top);
+ }
+ }
+
+ /** Computes LONG, SIZE and COMPAT parts of {@link Configuration#screenLayout}. */
+ static int computeScreenLayoutOverride(int sourceScreenLayout, int screenWidthDp,
+ int screenHeightDp) {
+ sourceScreenLayout = sourceScreenLayout
+ & (Configuration.SCREENLAYOUT_LONG_MASK | Configuration.SCREENLAYOUT_SIZE_MASK);
+ final int longSize = Math.max(screenWidthDp, screenHeightDp);
+ final int shortSize = Math.min(screenWidthDp, screenHeightDp);
+ return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
+ }
+
+ @Override
+ public int getActivityType() {
+ final int applicationType = super.getActivityType();
+ if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
+ return applicationType;
+ }
+ return getTopChild().getActivityType();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ // Task will animate differently.
+ if (mTaskFragmentOrganizer != null) {
+ mTmpPrevBounds.set(getBounds());
+ }
+
+ super.onConfigurationChanged(newParentConfig);
+
+ if (shouldStartChangeTransition(mTmpPrevBounds)) {
+ initializeChangeTransition(mTmpPrevBounds);
+ } else if (mTaskFragmentOrganizer != null) {
+ // Update the surface here instead of in the organizer so that we can make sure
+ // it can be synced with the surface freezer.
+ final SurfaceControl.Transaction t = getSyncTransaction();
+ updateSurfacePosition(t);
+ updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+ }
+
+ sendTaskFragmentInfoChanged();
+ }
+
+ /** Updates the surface size so that the sub windows cannot be shown out of bounds. */
+ private void updateOrganizedTaskFragmentSurfaceSize(SurfaceControl.Transaction t,
+ boolean forceUpdate) {
+ if (mTaskFragmentOrganizer == null) {
+ // We only want to update for organized TaskFragment. Task will handle itself.
+ return;
+ }
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
+ return;
+ }
+
+ final Rect bounds = getBounds();
+ final int width = bounds.width();
+ final int height = bounds.height();
+ if (!forceUpdate && width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+ return;
+ }
+ t.setWindowCrop(mSurfaceControl, width, height);
+ mLastSurfaceSize.set(width, height);
+ }
+
+ @Override
+ public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+ super.onAnimationLeashCreated(t, leash);
+ // Reset surface bounds for animation. It will be taken care by the animation leash, and
+ // reset again onAnimationLeashLost.
+ if (mTaskFragmentOrganizer != null
+ && (mLastSurfaceSize.x != 0 || mLastSurfaceSize.y != 0)) {
+ t.setWindowCrop(mSurfaceControl, 0, 0);
+ mLastSurfaceSize.set(0, 0);
+ }
+ }
+
+ @Override
+ public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+ super.onAnimationLeashLost(t);
+ // Update the surface bounds after animation.
+ if (mTaskFragmentOrganizer != null) {
+ updateOrganizedTaskFragmentSurfaceSize(t, true /* forceUpdate */);
+ }
+ }
+
+ /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
+ private boolean shouldStartChangeTransition(Rect startBounds) {
+ if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
+ return false;
+ }
+
+ return !startBounds.equals(getBounds());
+ }
+
+ @Override
+ void setSurfaceControl(SurfaceControl sc) {
+ super.setSurfaceControl(sc);
+ if (mTaskFragmentOrganizer != null) {
+ final SurfaceControl.Transaction t = getSyncTransaction();
+ updateSurfacePosition(t);
+ updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
+ // If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
+ // emit the callbacks now.
+ sendTaskFragmentAppeared();
+ }
+ }
+
+ void sendTaskFragmentInfoChanged() {
+ if (mTaskFragmentOrganizer != null) {
+ mTaskFragmentOrganizerController
+ .onTaskFragmentInfoChanged(mTaskFragmentOrganizer, this);
+ }
+ }
+
+ private void sendTaskFragmentAppeared() {
+ if (mTaskFragmentOrganizer != null) {
+ mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
+ }
+ }
+
+ private void sendTaskFragmentVanished() {
+ if (mTaskFragmentOrganizer != null) {
+ mTaskFragmentOrganizerController.onTaskFragmentVanished(mTaskFragmentOrganizer, this);
+ }
+ }
+
+ /**
+ * Returns a {@link TaskFragmentInfo} with information from this TaskFragment. Should not be
+ * called from {@link Task}.
+ */
+ TaskFragmentInfo getTaskFragmentInfo() {
+ List<IBinder> childActivities = new ArrayList<>();
+ for (int i = 0; i < getChildCount(); i++) {
+ final WindowContainer wc = getChildAt(i);
+ final ActivityRecord ar = wc.asActivityRecord();
+ if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null
+ && ar.info.processName.equals(mTaskFragmentOrganizerProcessName)
+ && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) {
+ // Only includes Activities that belong to the organizer process for security.
+ childActivities.add(ar.appToken);
+ }
+ }
+ final Point positionInParent = new Point();
+ getRelativePosition(positionInParent);
+ final int[] runningActivityCount = new int[1];
+ forAllActivities(a -> {
+ if (!a.finishing) {
+ runningActivityCount[0]++;
+ }
+ });
+ return new TaskFragmentInfo(
+ mFragmentToken,
+ mRemoteToken.toWindowContainerToken(),
+ getConfiguration(),
+ getChildCount() == 0,
+ runningActivityCount[0],
+ isVisible(),
+ childActivities,
+ positionInParent,
+ mClearedTaskForReuse);
+ }
+
+ @Nullable
+ IBinder getFragmentToken() {
+ return mFragmentToken;
+ }
+
+ @Nullable
+ ITaskFragmentOrganizer getTaskFragmentOrganizer() {
+ return mTaskFragmentOrganizer;
+ }
+
+ @Override
+ boolean isOrganized() {
+ return mTaskFragmentOrganizer != null;
+ }
+
+ /** Whether this is an organized {@link TaskFragment} and not a {@link Task}. */
+ final boolean isOrganizedTaskFragment() {
+ return mTaskFragmentOrganizer != null;
+ }
+
+ boolean isReadyToTransit() {
+ // We don't want to start the transition if the organized TaskFragment is empty, unless
+ // it is requested to be removed.
+ return !isOrganizedTaskFragment() || getTopNonFinishingActivity() != null
+ || mIsRemovalRequested;
+ }
+
+ /** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
+ void clearLastPausedActivity() {
+ forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
+ }
+
+ /**
+ * Sets {@link #mMinWidth} and {@link #mMinWidth} to this TaskFragment.
+ * It is usually set from the parent {@link Task} when adding the TaskFragment to the window
+ * hierarchy.
+ */
+ void setMinDimensions(int minWidth, int minHeight) {
+ if (asTask() != null) {
+ throw new UnsupportedOperationException("This method must not be used to Task. The "
+ + " minimum dimension of Task should be passed from Task constructor.");
+ }
+ mMinWidth = minWidth;
+ mMinHeight = minHeight;
+ }
+
+ @Override
+ void removeChild(WindowContainer child) {
+ removeChild(child, true /* removeSelfIfPossible */);
+ }
+
+ void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
+ super.removeChild(child);
+ if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) {
+ removeImmediately("removeLastChild " + child);
+ }
+ }
+
+ /**
+ * Requests to remove this task fragment. If it doesn't have children, it is removed
+ * immediately. Otherwise it will be removed until all activities are destroyed.
+ *
+ * @param withTransition Whether to use transition animation when removing activities. Set to
+ * {@code false} if this is invisible to user, e.g. display removal.
+ */
+ void remove(boolean withTransition, String reason) {
+ if (!hasChild()) {
+ removeImmediately(reason);
+ return;
+ }
+ mIsRemovalRequested = true;
+ forAllActivities(r -> {
+ if (withTransition) {
+ r.finishIfPossible(reason, false /* oomAdj */);
+ } else {
+ r.destroyIfPossible(reason);
+ }
+ });
+ }
+
+ void setDelayLastActivityRemoval(boolean delay) {
+ if (!mIsEmbedded) {
+ Slog.w(TAG, "Set delaying last activity removal on a non-embedded TF.");
+ }
+ mDelayLastActivityRemoval = delay;
+ }
+
+ boolean isDelayLastActivityRemoval() {
+ return mDelayLastActivityRemoval;
+ }
+
+ boolean shouldDeferRemoval() {
+ if (!hasChild()) {
+ return false;
+ }
+ return isAnimating(TRANSITION | CHILDREN, WindowState.EXIT_ANIMATING_TYPES)
+ || inTransition();
+ }
+
+ @Override
+ boolean handleCompleteDeferredRemoval() {
+ if (shouldDeferRemoval()) {
+ return true;
+ }
+ return super.handleCompleteDeferredRemoval();
+ }
+
+ /** The overridden method must call {@link #removeImmediately()} instead of super. */
+ void removeImmediately(String reason) {
+ Slog.d(TAG, "Remove task fragment: " + reason);
+ removeImmediately();
+ }
+
+ @Override
+ void removeImmediately() {
+ mIsRemovalRequested = false;
+ resetAdjacentTaskFragment();
+ super.removeImmediately();
+ sendTaskFragmentVanished();
+ }
+
+ @Override
+ Dimmer getDimmer() {
+ // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment.
+ if (asTask() == null) {
+ return mDimmer;
+ }
+
+ return super.getDimmer();
+ }
+
+ @Override
+ void prepareSurfaces() {
+ if (asTask() != null) {
+ super.prepareSurfaces();
+ return;
+ }
+
+ mDimmer.resetDimStates();
+ super.prepareSurfaces();
+
+ // Bounds need to be relative, as the dim layer is a child.
+ final Rect dimBounds = getBounds();
+ dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
+ if (mDimmer.updateDims(getPendingTransaction(), dimBounds)) {
+ scheduleAnimation();
+ }
+ }
+
+ @Override
+ boolean canBeAnimationTarget() {
+ return true;
+ }
+
+ @Override
+ boolean fillsParent() {
+ // From the perspective of policy, we still want to report that this task fills parent
+ // in fullscreen windowing mode even it doesn't match parent bounds because there will be
+ // letterbox around its real content.
+ return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
+ }
+
+ boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
+ boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
+ boolean printed = false;
+ Runnable headerPrinter = () -> {
+ if (needSep) {
+ pw.println();
+ }
+ if (header != null) {
+ header.run();
+ }
+
+ dumpInner(prefix, pw, dumpAll, dumpPackage);
+ };
+
+ if (dumpPackage == null) {
+ // If we are not filtering by package, we want to print absolutely everything,
+ // so always print the header even if there are no tasks/activities inside.
+ headerPrinter.run();
+ headerPrinter = null;
+ printed = true;
+ }
+
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ WindowContainer child = mChildren.get(i);
+ if (child.asTaskFragment() != null) {
+ printed |= child.asTaskFragment().dump(prefix + " ", fd, pw, dumpAll,
+ dumpClient, dumpPackage, needSep, headerPrinter);
+ } else if (child.asActivityRecord() != null) {
+ ActivityRecord.dumpActivity(fd, pw, i, child.asActivityRecord(), prefix + " ",
+ "Hist ", true, !dumpAll, dumpClient, dumpPackage, false, headerPrinter,
+ getTask());
+ }
+ }
+
+ return printed;
+ }
+
+ void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
+ pw.print(prefix); pw.print("* "); pw.println(this);
+ final Rect bounds = getRequestedOverrideBounds();
+ if (!bounds.isEmpty()) {
+ pw.println(prefix + " mBounds=" + bounds);
+ }
+ if (mIsRemovalRequested) {
+ pw.println(prefix + " mIsRemovalRequested=true");
+ }
+ if (dumpAll) {
+ printThisActivity(pw, mLastPausedActivity, dumpPackage, false,
+ prefix + " mLastPausedActivity: ", null);
+ }
+ }
+
+ @Override
+ void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+ super.dump(pw, prefix, dumpAll);
+ pw.println(prefix + "bounds=" + getBounds().toShortString());
+ final String doublePrefix = prefix + " ";
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowContainer<?> child = mChildren.get(i);
+ pw.println(prefix + "* " + child);
+ // Only dump non-activity because full activity info is already printed by
+ // RootWindowContainer#dumpActivities.
+ if (child.asActivityRecord() == null) {
+ child.dump(pw, doublePrefix, dumpAll);
+ }
+ }
+ }
+
+ @Override
+ void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(HASH_CODE, System.identityHashCode(this));
+ final ActivityRecord topActivity = topRunningActivity();
+ proto.write(USER_ID, topActivity != null ? topActivity.mUserId : USER_NULL);
+ proto.write(TITLE, topActivity != null ? topActivity.intent.getComponent()
+ .flattenToShortString() : "TaskFragment");
+ proto.end(token);
+ }
+
+ @Override
+ long getProtoFieldId() {
+ return TASK_FRAGMENT;
+ }
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto, long fieldId,
+ @WindowTraceLogLevel int logLevel) {
+ if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+ return;
+ }
+
+ final long token = proto.start(fieldId);
+
+ super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
+
+ proto.write(DISPLAY_ID, getDisplayId());
+ proto.write(ACTIVITY_TYPE, getActivityType());
+ proto.write(MIN_WIDTH, mMinWidth);
+ proto.write(MIN_HEIGHT, mMinHeight);
+
+ proto.end(token);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
new file mode 100644
index 000000000000..123ca889c73e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -0,0 +1,624 @@
+/*
+ * 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.wm;
+
+import static android.window.TaskFragmentOrganizer.putExceptionInBundle;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.RemoteAnimationDefinition;
+import android.window.ITaskFragmentOrganizer;
+import android.window.ITaskFragmentOrganizerController;
+import android.window.TaskFragmentInfo;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Stores and manages the client {@link android.window.TaskFragmentOrganizer}.
+ */
+public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerController.Stub {
+ private static final String TAG = "TaskFragmentOrganizerController";
+
+ private final ActivityTaskManagerService mAtmService;
+ private final WindowManagerGlobalLock mGlobalLock;
+ /**
+ * A Map which manages the relationship between
+ * {@link ITaskFragmentOrganizer} and {@link TaskFragmentOrganizerState}
+ */
+ private final ArrayMap<IBinder, TaskFragmentOrganizerState> mTaskFragmentOrganizerState =
+ new ArrayMap<>();
+ /**
+ * A List which manages the TaskFragment pending event {@link PendingTaskFragmentEvent}
+ */
+ private final ArrayList<PendingTaskFragmentEvent> mPendingTaskFragmentEvents =
+ new ArrayList<>();
+
+ TaskFragmentOrganizerController(ActivityTaskManagerService atm) {
+ mAtmService = atm;
+ mGlobalLock = atm.mGlobalLock;
+ }
+
+ /**
+ * A class to manage {@link ITaskFragmentOrganizer} and its organized
+ * {@link TaskFragment TaskFragments}.
+ */
+ private class TaskFragmentOrganizerState implements IBinder.DeathRecipient {
+ private final ArrayList<TaskFragment> mOrganizedTaskFragments = new ArrayList<>();
+ private final ITaskFragmentOrganizer mOrganizer;
+ private final Map<TaskFragment, TaskFragmentInfo> mLastSentTaskFragmentInfos =
+ new WeakHashMap<>();
+ private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs =
+ new WeakHashMap<>();
+
+ /**
+ * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(
+ * RemoteAnimationDefinition)
+ */
+ @Nullable
+ private RemoteAnimationDefinition mRemoteAnimationDefinition;
+
+ TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ try {
+ mOrganizer.asBinder().linkToDeath(this, 0 /*flags*/);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "TaskFragmentOrganizer failed to register death recipient");
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mGlobalLock) {
+ removeOrganizer(mOrganizer);
+ }
+ }
+
+ /**
+ * @return {@code true} if taskFragment is organized and not sent the appeared event before.
+ */
+ boolean addTaskFragment(TaskFragment taskFragment) {
+ if (taskFragment.mTaskFragmentAppearedSent) {
+ return false;
+ }
+ if (mOrganizedTaskFragments.contains(taskFragment)) {
+ return false;
+ }
+ mOrganizedTaskFragments.add(taskFragment);
+ return true;
+ }
+
+ void removeTaskFragment(TaskFragment taskFragment) {
+ mOrganizedTaskFragments.remove(taskFragment);
+ }
+
+ void dispose() {
+ while (!mOrganizedTaskFragments.isEmpty()) {
+ final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
+ taskFragment.removeImmediately();
+ mOrganizedTaskFragments.remove(taskFragment);
+ }
+ mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
+ }
+
+ void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment appeared name=%s", tf.getName());
+ final TaskFragmentInfo info = tf.getTaskFragmentInfo();
+ try {
+ organizer.onTaskFragmentAppeared(info);
+ mLastSentTaskFragmentInfos.put(tf, info);
+ tf.mTaskFragmentAppearedSent = true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskFragmentAppeared callback", e);
+ }
+ onTaskFragmentParentInfoChanged(organizer, tf);
+ }
+
+ void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment vanished name=%s", tf.getName());
+ try {
+ organizer.onTaskFragmentVanished(tf.getTaskFragmentInfo());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskFragmentVanished callback", e);
+ }
+ tf.mTaskFragmentAppearedSent = false;
+ mLastSentTaskFragmentInfos.remove(tf);
+ mLastSentTaskFragmentParentConfigs.remove(tf);
+ }
+
+ void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+ // Parent config may have changed. The controller will check if there is any important
+ // config change for the organizer.
+ onTaskFragmentParentInfoChanged(organizer, tf);
+
+ // Check if the info is different from the last reported info.
+ final TaskFragmentInfo info = tf.getTaskFragmentInfo();
+ final TaskFragmentInfo lastInfo = mLastSentTaskFragmentInfos.get(tf);
+ if (info.equalsForTaskFragmentOrganizer(lastInfo) && configurationsAreEqualForOrganizer(
+ info.getConfiguration(), lastInfo.getConfiguration())) {
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "TaskFragment info changed name=%s",
+ tf.getName());
+ try {
+ organizer.onTaskFragmentInfoChanged(tf.getTaskFragmentInfo());
+ mLastSentTaskFragmentInfos.put(tf, info);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskFragmentInfoChanged callback", e);
+ }
+ }
+
+ void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment tf) {
+ // Check if the parent info is different from the last reported parent info.
+ if (tf.getParent() == null || tf.getParent().asTask() == null) {
+ mLastSentTaskFragmentParentConfigs.remove(tf);
+ return;
+ }
+ final Task parent = tf.getParent().asTask();
+ final Configuration parentConfig = parent.getConfiguration();
+ final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(tf);
+ if (configurationsAreEqualForOrganizer(parentConfig, lastParentConfig)) {
+ return;
+ }
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "TaskFragment parent info changed name=%s parentTaskId=%d",
+ tf.getName(), parent.mTaskId);
+ try {
+ organizer.onTaskFragmentParentInfoChanged(tf.getFragmentToken(), parentConfig);
+ mLastSentTaskFragmentParentConfigs.put(tf, new Configuration(parentConfig));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskFragmentParentInfoChanged callback", e);
+ }
+ }
+
+ void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
+ Throwable exception) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Sending TaskFragment error exception=%s", exception.toString());
+ final Bundle exceptionBundle = putExceptionInBundle(exception);
+ try {
+ organizer.onTaskFragmentError(errorCallbackToken, exceptionBundle);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskFragmentError callback", e);
+ }
+ }
+ }
+
+ @Override
+ public void registerOrganizer(ITaskFragmentOrganizer organizer) {
+ final int pid = Binder.getCallingPid();
+ final long uid = Binder.getCallingUid();
+ synchronized (mGlobalLock) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Register task fragment organizer=%s uid=%d pid=%d",
+ organizer.asBinder(), uid, pid);
+ if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ throw new IllegalStateException(
+ "Replacing existing organizer currently unsupported");
+ }
+ mTaskFragmentOrganizerState.put(organizer.asBinder(),
+ new TaskFragmentOrganizerState(organizer));
+ }
+ }
+
+ @Override
+ public void unregisterOrganizer(ITaskFragmentOrganizer organizer) {
+ validateAndGetState(organizer);
+ final int pid = Binder.getCallingPid();
+ final long uid = Binder.getCallingUid();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Unregister task fragment organizer=%s uid=%d pid=%d",
+ organizer.asBinder(), uid, pid);
+ removeOrganizer(organizer);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public void registerRemoteAnimations(ITaskFragmentOrganizer organizer,
+ RemoteAnimationDefinition definition) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ synchronized (mGlobalLock) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Register remote animations for organizer=%s uid=%d pid=%d",
+ organizer.asBinder(), uid, pid);
+ final TaskFragmentOrganizerState organizerState =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ if (organizerState == null) {
+ throw new IllegalStateException("The organizer hasn't been registered.");
+ }
+ if (organizerState.mRemoteAnimationDefinition != null) {
+ throw new IllegalStateException(
+ "The organizer has already registered remote animations="
+ + organizerState.mRemoteAnimationDefinition);
+ }
+
+ definition.setCallingPidUid(pid, uid);
+ organizerState.mRemoteAnimationDefinition = definition;
+ }
+ }
+
+ @Override
+ public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer) {
+ final int pid = Binder.getCallingPid();
+ final long uid = Binder.getCallingUid();
+ synchronized (mGlobalLock) {
+ ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+ "Unregister remote animations for organizer=%s uid=%d pid=%d",
+ organizer.asBinder(), uid, pid);
+ final TaskFragmentOrganizerState organizerState =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ if (organizerState == null) {
+ Slog.e(TAG, "The organizer hasn't been registered.");
+ return;
+ }
+
+ organizerState.mRemoteAnimationDefinition = null;
+ }
+ }
+
+ /** Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. */
+ @Nullable
+ public RemoteAnimationDefinition getRemoteAnimationDefinition(
+ ITaskFragmentOrganizer organizer) {
+ synchronized (mGlobalLock) {
+ final TaskFragmentOrganizerState organizerState =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ return organizerState != null ? organizerState.mRemoteAnimationDefinition : null;
+ }
+ }
+
+ void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+ if (!state.addTaskFragment(taskFragment)) {
+ return;
+ }
+ PendingTaskFragmentEvent pendingEvent = getPendingTaskFragmentEvent(taskFragment,
+ PendingTaskFragmentEvent.EVENT_APPEARED);
+ if (pendingEvent == null) {
+ pendingEvent = new PendingTaskFragmentEvent(taskFragment, organizer,
+ PendingTaskFragmentEvent.EVENT_APPEARED);
+ mPendingTaskFragmentEvents.add(pendingEvent);
+ }
+ }
+
+ void onTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ handleTaskFragmentInfoChanged(organizer, taskFragment,
+ PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
+ }
+
+ void onTaskFragmentParentInfoChanged(ITaskFragmentOrganizer organizer,
+ TaskFragment taskFragment) {
+ handleTaskFragmentInfoChanged(organizer, taskFragment,
+ PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED);
+ }
+
+ private void handleTaskFragmentInfoChanged(ITaskFragmentOrganizer organizer,
+ TaskFragment taskFragment, int eventType) {
+ validateAndGetState(organizer);
+ if (!taskFragment.mTaskFragmentAppearedSent) {
+ // Skip if TaskFragment still not appeared.
+ return;
+ }
+ PendingTaskFragmentEvent pendingEvent = getLastPendingLifecycleEvent(taskFragment);
+ if (pendingEvent == null) {
+ pendingEvent = new PendingTaskFragmentEvent(taskFragment, organizer, eventType);
+ } else {
+ if (pendingEvent.mEventType == PendingTaskFragmentEvent.EVENT_VANISHED) {
+ // Skipped the info changed event if vanished event is pending.
+ return;
+ }
+ // Remove and add for re-ordering.
+ mPendingTaskFragmentEvents.remove(pendingEvent);
+ }
+ mPendingTaskFragmentEvents.add(pendingEvent);
+ }
+
+ void onTaskFragmentVanished(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
+ final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+ for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+ PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
+ if (taskFragment == entry.mTaskFragment) {
+ mPendingTaskFragmentEvents.remove(i);
+ if (entry.mEventType == PendingTaskFragmentEvent.EVENT_APPEARED) {
+ // If taskFragment appeared callback is pending, ignore the vanished request.
+ return;
+ }
+ }
+ }
+ if (!taskFragment.mTaskFragmentAppearedSent) {
+ return;
+ }
+ PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent(taskFragment,
+ organizer, PendingTaskFragmentEvent.EVENT_VANISHED);
+ mPendingTaskFragmentEvents.add(pendingEvent);
+ state.removeTaskFragment(taskFragment);
+ }
+
+ void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
+ Throwable exception) {
+ validateAndGetState(organizer);
+ Slog.w(TAG, "onTaskFragmentError ", exception);
+ PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent(organizer,
+ errorCallbackToken, exception, PendingTaskFragmentEvent.EVENT_ERROR);
+ mPendingTaskFragmentEvents.add(pendingEvent);
+ }
+
+ private void removeOrganizer(ITaskFragmentOrganizer organizer) {
+ final TaskFragmentOrganizerState state = validateAndGetState(organizer);
+ // remove all of the children of the organized TaskFragment
+ state.dispose();
+ mTaskFragmentOrganizerState.remove(organizer.asBinder());
+ }
+
+ /**
+ * Makes sure that the organizer has been correctly registered to prevent any Sidecar
+ * implementation from organizing {@link TaskFragment} without registering first. In such case,
+ * we wouldn't register {@link DeathRecipient} for the organizer, and might not remove the
+ * {@link TaskFragment} after the organizer process died.
+ */
+ private TaskFragmentOrganizerState validateAndGetState(ITaskFragmentOrganizer organizer) {
+ final TaskFragmentOrganizerState state =
+ mTaskFragmentOrganizerState.get(organizer.asBinder());
+ if (state == null) {
+ throw new IllegalArgumentException(
+ "TaskFragmentOrganizer has not been registered. Organizer=" + organizer);
+ }
+ return state;
+ }
+
+ /**
+ * A class to store {@link ITaskFragmentOrganizer} and its organized
+ * {@link TaskFragment TaskFragments} with different pending event request.
+ */
+ private static class PendingTaskFragmentEvent {
+ static final int EVENT_APPEARED = 0;
+ static final int EVENT_VANISHED = 1;
+ static final int EVENT_INFO_CHANGED = 2;
+ static final int EVENT_PARENT_INFO_CHANGED = 3;
+ static final int EVENT_ERROR = 4;
+
+ @IntDef(prefix = "EVENT_", value = {
+ EVENT_APPEARED,
+ EVENT_VANISHED,
+ EVENT_INFO_CHANGED,
+ EVENT_PARENT_INFO_CHANGED,
+ EVENT_ERROR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
+ @EventType
+ private final int mEventType;
+ private final ITaskFragmentOrganizer mTaskFragmentOrg;
+ private final TaskFragment mTaskFragment;
+ private final IBinder mErrorCallback;
+ private final Throwable mException;
+ // Set when the event is deferred due to the host task is invisible. The defer time will
+ // be the last active time of the host task.
+ private long mDeferTime;
+
+ private PendingTaskFragmentEvent(TaskFragment taskFragment,
+ ITaskFragmentOrganizer taskFragmentOrg, @EventType int eventType) {
+ this(taskFragment, taskFragmentOrg, null /* errorCallback */,
+ null /* exception */, eventType);
+
+ }
+
+ private PendingTaskFragmentEvent(ITaskFragmentOrganizer taskFragmentOrg,
+ IBinder errorCallback, Throwable exception, @EventType int eventType) {
+ this(null /* taskFragment */, taskFragmentOrg, errorCallback, exception,
+ eventType);
+ }
+
+ private PendingTaskFragmentEvent(TaskFragment taskFragment,
+ ITaskFragmentOrganizer taskFragmentOrg, IBinder errorCallback, Throwable exception,
+ @EventType int eventType) {
+ mTaskFragment = taskFragment;
+ mTaskFragmentOrg = taskFragmentOrg;
+ mErrorCallback = errorCallback;
+ mException = exception;
+ mEventType = eventType;
+ }
+
+ /**
+ * @return {@code true} if the pending event is related with taskFragment created, vanished
+ * and information changed.
+ */
+ boolean isLifecycleEvent() {
+ switch (mEventType) {
+ case EVENT_APPEARED:
+ case EVENT_VANISHED:
+ case EVENT_INFO_CHANGED:
+ case EVENT_PARENT_INFO_CHANGED:
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+
+ @Nullable
+ private PendingTaskFragmentEvent getLastPendingLifecycleEvent(TaskFragment tf) {
+ for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+ PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
+ if (tf == entry.mTaskFragment && entry.isLifecycleEvent()) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private PendingTaskFragmentEvent getPendingTaskFragmentEvent(TaskFragment taskFragment,
+ int type) {
+ for (int i = mPendingTaskFragmentEvents.size() - 1; i >= 0; i--) {
+ PendingTaskFragmentEvent entry = mPendingTaskFragmentEvents.get(i);
+ if (taskFragment == entry.mTaskFragment && type == entry.mEventType) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ private boolean shouldSendEventWhenTaskInvisible(@NonNull Task task,
+ @NonNull PendingTaskFragmentEvent event) {
+ final TaskFragmentOrganizerState state =
+ mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
+ final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
+ final TaskFragmentInfo info = event.mTaskFragment.getTaskFragmentInfo();
+ // Send an info changed callback if this event is for the last activities to finish in a
+ // Task so that the {@link TaskFragmentOrganizer} can delete this TaskFragment. Otherwise,
+ // the Task may be removed before it becomes visible again to send this event because it no
+ // longer has activities. As a result, the organizer will never get this info changed event
+ // and will not delete the TaskFragment because the organizer thinks the TaskFragment still
+ // has running activities.
+ return event.mEventType == PendingTaskFragmentEvent.EVENT_INFO_CHANGED
+ && task.topRunningActivity() == null && lastInfo != null
+ && lastInfo.getRunningActivityCount() > 0 && info.getRunningActivityCount() == 0;
+ }
+
+ void dispatchPendingEvents() {
+ if (mAtmService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
+ || mPendingTaskFragmentEvents.isEmpty()) {
+ return;
+ }
+
+ final ArrayList<Task> visibleTasks = new ArrayList<>();
+ final ArrayList<Task> invisibleTasks = new ArrayList<>();
+ final ArrayList<PendingTaskFragmentEvent> candidateEvents = new ArrayList<>();
+ for (int i = 0, n = mPendingTaskFragmentEvents.size(); i < n; i++) {
+ final PendingTaskFragmentEvent event = mPendingTaskFragmentEvents.get(i);
+ final Task task = event.mTaskFragment != null ? event.mTaskFragment.getTask() : null;
+ if (task != null && (task.lastActiveTime <= event.mDeferTime
+ || !(isTaskVisible(task, visibleTasks, invisibleTasks)
+ || shouldSendEventWhenTaskInvisible(task, event)))) {
+ // Defer sending events to the TaskFragment until the host task is active again.
+ event.mDeferTime = task.lastActiveTime;
+ continue;
+ }
+ candidateEvents.add(event);
+ }
+ final int numEvents = candidateEvents.size();
+ for (int i = 0; i < numEvents; i++) {
+ dispatchEvent(candidateEvents.get(i));
+ }
+ if (numEvents > 0) {
+ mPendingTaskFragmentEvents.removeAll(candidateEvents);
+ }
+ }
+
+ private static boolean isTaskVisible(Task task, ArrayList<Task> knownVisibleTasks,
+ ArrayList<Task> knownInvisibleTasks) {
+ if (knownVisibleTasks.contains(task)) {
+ return true;
+ }
+ if (knownInvisibleTasks.contains(task)) {
+ return false;
+ }
+ if (task.shouldBeVisible(null /* starting */)) {
+ knownVisibleTasks.add(task);
+ return true;
+ } else {
+ knownInvisibleTasks.add(task);
+ return false;
+ }
+ }
+
+ void dispatchPendingInfoChangedEvent(TaskFragment taskFragment) {
+ PendingTaskFragmentEvent event = getPendingTaskFragmentEvent(taskFragment,
+ PendingTaskFragmentEvent.EVENT_INFO_CHANGED);
+ if (event == null) {
+ return;
+ }
+
+ dispatchEvent(event);
+ mPendingTaskFragmentEvents.remove(event);
+ }
+
+ private void dispatchEvent(PendingTaskFragmentEvent event) {
+ final ITaskFragmentOrganizer taskFragmentOrg = event.mTaskFragmentOrg;
+ final TaskFragment taskFragment = event.mTaskFragment;
+ final TaskFragmentOrganizerState state =
+ mTaskFragmentOrganizerState.get(taskFragmentOrg.asBinder());
+ if (state == null) {
+ return;
+ }
+ switch (event.mEventType) {
+ case PendingTaskFragmentEvent.EVENT_APPEARED:
+ state.onTaskFragmentAppeared(taskFragmentOrg, taskFragment);
+ break;
+ case PendingTaskFragmentEvent.EVENT_VANISHED:
+ state.onTaskFragmentVanished(taskFragmentOrg, taskFragment);
+ break;
+ case PendingTaskFragmentEvent.EVENT_INFO_CHANGED:
+ state.onTaskFragmentInfoChanged(taskFragmentOrg, taskFragment);
+ break;
+ case PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED:
+ state.onTaskFragmentParentInfoChanged(taskFragmentOrg, taskFragment);
+ break;
+ case PendingTaskFragmentEvent.EVENT_ERROR:
+ state.onTaskFragmentError(taskFragmentOrg, event.mErrorCallback,
+ event.mException);
+ }
+ }
+
+ // TODO(b/204399167): change to push the embedded state to the client side
+ @Override
+ public boolean isActivityEmbedded(IBinder activityToken) {
+ synchronized (mGlobalLock) {
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
+ if (activity == null) {
+ return false;
+ }
+ final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
+ if (taskFragment == null) {
+ return false;
+ }
+ final Task parentTask = taskFragment.getTask();
+ if (parentTask != null) {
+ final Rect taskBounds = parentTask.getBounds();
+ final Rect taskFragBounds = taskFragment.getBounds();
+ return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+ }
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index f43cd7a80ede..b8ceb4a4f421 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -261,7 +261,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
if (launchMode == WINDOWING_MODE_PINNED) {
if (DEBUG) appendLog("picture-in-picture");
} else if (!root.isResizeable()) {
- if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea)) {
+ if (shouldLaunchUnresizableAppInFreeform(root, suggestedDisplayArea, options)) {
launchMode = WINDOWING_MODE_FREEFORM;
if (outParams.mBounds.isEmpty()) {
getTaskBounds(root, suggestedDisplayArea, layout, launchMode,
@@ -617,7 +617,11 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
}
private boolean shouldLaunchUnresizableAppInFreeform(ActivityRecord activity,
- TaskDisplayArea displayArea) {
+ TaskDisplayArea displayArea, @Nullable ActivityOptions options) {
+ if (options != null && options.getLaunchWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ // Do not launch the activity in freeform if it explicitly requested fullscreen mode.
+ return false;
+ }
if (!activity.supportsFreeformInDisplayArea(displayArea) || activity.isResizeable()) {
return false;
}
@@ -713,7 +717,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
}
// First we get the default size we want.
- getDefaultFreeformSize(displayArea, layout, orientation, mTmpBounds);
+ getDefaultFreeformSize(root.info, displayArea, layout, orientation, mTmpBounds);
if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
// We're here because either input parameters specified initial bounds, or the suggested
// bounds have the same size of the default freeform size. We should use the suggested
@@ -781,7 +785,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
return orientation;
}
- private void getDefaultFreeformSize(@NonNull TaskDisplayArea displayArea,
+ private void getDefaultFreeformSize(@NonNull ActivityInfo info,
+ @NonNull TaskDisplayArea displayArea,
@NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
// Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large
// dimension of default size is the small dimension of displayArea size, and the small
@@ -812,11 +817,38 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
- // Final result.
+ // Aspect ratio requirements.
+ final float minAspectRatio = info.getMinAspectRatio(orientation);
+ final float maxAspectRatio = info.getMaxAspectRatio();
+
final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth));
final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight));
+ final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
+
+ // Adjust the width and height to the aspect ratio requirements.
+ int adjWidth = width;
+ int adjHeight = height;
+ if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) {
+ // The aspect ratio is below the minimum, adjust it to the minimum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / minAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / minAspectRatio + 0.5f);
+ }
+ } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) {
+ // The aspect ratio exceeds the maximum, adjust it to the maximum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f);
+ }
+ }
- bounds.set(0, 0, width, height);
+ bounds.set(0, 0, adjWidth, adjHeight);
bounds.offset(stableBounds.left, stableBounds.top);
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 88467baa6c34..3d5f9881e044 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,24 +16,19 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
import static com.android.internal.protolog.ProtoLogGroup.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;
-import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
-import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
+import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.ParceledListSlice;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -45,6 +40,7 @@ import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -69,21 +65,6 @@ import java.util.function.Consumer;
class TaskOrganizerController extends ITaskOrganizerController.Stub {
private static final String TAG = "TaskOrganizerController";
- /**
- * Masks specifying which configurations are important to report back to an organizer when
- * changed.
- */
- private static final int REPORT_CONFIGS = CONTROLLABLE_CONFIGS;
- private static final int REPORT_WINDOW_CONFIGS = CONTROLLABLE_WINDOW_CONFIGS;
-
- // The set of modes that are currently supports
- // TODO: Remove once the task organizer can support all modes
- @VisibleForTesting
- static final int[] UNSUPPORTED_WINDOWING_MODES = {
- WINDOWING_MODE_UNDEFINED,
- WINDOWING_MODE_FREEFORM
- };
-
private class DeathRecipient implements IBinder.DeathRecipient {
ITaskOrganizer mTaskOrganizer;
@@ -122,109 +103,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
return mTaskOrganizer.asBinder();
}
- void addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
- TaskSnapshot taskSnapshot) {
- final StartingWindowInfo info = task.getStartingWindowInfo(activity);
- if (launchTheme != 0) {
- info.splashScreenThemeResId = launchTheme;
- }
- info.mTaskSnapshot = taskSnapshot;
- // make this happen prior than prepare surface
- try {
- mTaskOrganizer.addStartingWindow(info, activity.token);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onTaskStart callback", e);
- }
- }
-
- // Capture the animation surface control for activity's main window
- private class StartingWindowAnimationAdaptor implements AnimationAdapter {
- private SurfaceControl mAnimationLeash;
- @Override
- public boolean getShowWallpaper() {
- return false;
- }
-
- @Override
- public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
- mAnimationLeash = animationLeash;
- }
-
- @Override
- public void onAnimationCancelled(SurfaceControl animationLeash) {
- if (mAnimationLeash == animationLeash) {
- mAnimationLeash = null;
- }
- }
-
- @Override
- public long getDurationHint() {
- return 0;
- }
-
- @Override
- public long getStatusBarTransitionsStartTime() {
- return 0;
- }
-
- @Override
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
- pw.print(mAnimationLeash);
- pw.println();
- }
-
- @Override
- public void dumpDebug(ProtoOutputStream proto) {
- }
- }
-
- void removeStartingWindow(Task task, boolean prepareAnimation) {
- SurfaceControl windowAnimationLeash = null;
- Rect mainFrame = null;
- final boolean playShiftUpAnimation = !task.inMultiWindowMode();
- if (prepareAnimation && playShiftUpAnimation) {
- final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
- if (topActivity != null) {
- final WindowState mainWindow =
- topActivity.findMainWindow(false/* includeStartingApp */);
- if (mainWindow != null) {
- final StartingWindowAnimationAdaptor adaptor =
- new StartingWindowAnimationAdaptor();
- final SurfaceControl.Transaction t = mainWindow.getPendingTransaction();
- mainWindow.startAnimation(t, adaptor, false,
- ANIMATION_TYPE_STARTING_REVEAL);
- windowAnimationLeash = adaptor.mAnimationLeash;
- mainFrame = mainWindow.getRelativeFrame();
- t.setPosition(windowAnimationLeash, mainFrame.left, mainFrame.top);
- }
- }
- }
- try {
- mTaskOrganizer.removeStartingWindow(task.mTaskId, windowAnimationLeash,
- mainFrame, prepareAnimation);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
- }
- }
-
- void copySplashScreenView(Task task) {
- try {
- mTaskOrganizer.copySplashScreenView(task.mTaskId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
- }
- }
-
- void onAppSplashScreenViewRemoved(Task task) {
- try {
- mTaskOrganizer.onAppSplashScreenViewRemoved(task.mTaskId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e);
- }
- }
-
SurfaceControl prepareLeash(Task task, String reason) {
return new SurfaceControl(task.getSurfaceControl(), reason);
}
@@ -311,23 +189,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mUid = uid;
}
- void addStartingWindow(Task t, ActivityRecord activity, int launchTheme,
- TaskSnapshot taskSnapshot) {
- mOrganizer.addStartingWindow(t, activity, launchTheme, taskSnapshot);
- }
-
- void removeStartingWindow(Task t, boolean prepareAnimation) {
- mOrganizer.removeStartingWindow(t, prepareAnimation);
- }
-
- void copySplashScreenView(Task t) {
- mOrganizer.copySplashScreenView(t);
- }
-
- public void onAppSplashScreenViewRemoved(Task t) {
- mOrganizer.onAppSplashScreenViewRemoved(t);
- }
-
/**
* Register this task with this state, but doesn't trigger the task appeared callback to
* the organizer.
@@ -389,6 +250,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mOrganizer.mTaskOrganizer, t);
}
}
+ if (mService.getTransitionController().isShellTransitionsEnabled()) {
+ // dispose is only called outside of transitions (eg during unregister). Since
+ // we "migrate" surfaces when replacing organizers, visibility gets delegated
+ // to transitions; however, since there is no transition at this point, we have
+ // to manually show the surface here.
+ if (t.mTaskOrganizer != null && t.getSurfaceControl() != null) {
+ t.getSyncTransaction().show(t.getSurfaceControl());
+ }
+ }
}
// Remove organizer state after removing tasks so we get a chance to send
@@ -481,7 +351,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
- synchronized (mGlobalLock) {
+ final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
+ final Runnable withGlobalLock = () -> {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Register task organizer=%s uid=%d",
organizer.asBinder(), uid);
if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {
@@ -490,24 +361,27 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
new TaskOrganizerState(organizer, uid));
}
- final ArrayList<TaskAppearedInfo> taskInfos = new ArrayList<>();
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
mService.mRootWindowContainer.forAllTasks((task) -> {
- if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, task.getWindowingMode())) {
- return;
- }
-
boolean returnTask = !task.mCreatedByOrganizer;
task.updateTaskOrganizerState(true /* forceUpdate */,
returnTask /* skipTaskAppeared */);
if (returnTask) {
SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task,
"TaskOrganizerController.registerTaskOrganizer");
- taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
+ taskInfos.add(
+ new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
}
});
- return new ParceledListSlice<>(taskInfos);
+ };
+ if (mService.getTransitionController().isShellTransitionsEnabled()) {
+ mService.getTransitionController().mRunningLock.runWhenIdle(1000, withGlobalLock);
+ } else {
+ synchronized (mGlobalLock) {
+ withGlobalLock.run();
+ }
}
+ return new ParceledListSlice<>(taskInfos);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -519,7 +393,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
- synchronized (mGlobalLock) {
+ final Runnable withGlobalLock = () -> {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state == null) {
return;
@@ -528,6 +402,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
organizer.asBinder(), uid);
state.unlinkDeath();
state.dispose();
+ };
+ if (mService.getTransitionController().isShellTransitionsEnabled()) {
+ mService.getTransitionController().mRunningLock.runWhenIdle(1000, withGlobalLock);
+ } else {
+ synchronized (mGlobalLock) {
+ withGlobalLock.run();
+ }
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -537,46 +418,136 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
/**
* @return the task organizer key for a given windowing mode.
*/
- ITaskOrganizer getTaskOrganizer(int windowingMode) {
- return isSupportedWindowingMode(windowingMode)
- ? mTaskOrganizers.peekLast()
- : null;
+ ITaskOrganizer getTaskOrganizer() {
+ return mTaskOrganizers.peekLast();
}
- boolean isSupportedWindowingMode(int winMode) {
- return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode);
+ // Capture the animation surface control for activity's main window
+ static class StartingWindowAnimationAdaptor implements AnimationAdapter {
+ SurfaceControl mAnimationLeash;
+ @Override
+ public boolean getShowWallpaper() {
+ return false;
+ }
+
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+ int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ mAnimationLeash = animationLeash;
+ }
+
+ @Override
+ public void onAnimationCancelled(SurfaceControl animationLeash) {
+ if (mAnimationLeash == animationLeash) {
+ mAnimationLeash = null;
+ }
+ }
+
+ @Override
+ public long getDurationHint() {
+ return 0;
+ }
+
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return 0;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
+ pw.print(mAnimationLeash);
+ pw.println();
+ }
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto) {
+ }
+ }
+
+ static SurfaceControl applyStartingWindowAnimation(WindowContainer window) {
+ final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
+ window.startAnimation(window.getPendingTransaction(), adaptor, false,
+ ANIMATION_TYPE_STARTING_REVEAL);
+ return adaptor.mAnimationLeash;
}
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
TaskSnapshot taskSnapshot) {
final Task rootTask = task.getRootTask();
- if (rootTask == null || rootTask.mTaskOrganizer == null || activity.mStartingData == null) {
+ if (rootTask == null || activity.mStartingData == null) {
+ return false;
+ }
+ final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ if (lastOrganizer == null) {
+ return false;
+ }
+ final StartingWindowInfo info = task.getStartingWindowInfo(activity);
+ if (launchTheme != 0) {
+ info.splashScreenThemeResId = launchTheme;
+ }
+ info.taskSnapshot = taskSnapshot;
+ // make this happen prior than prepare surface
+ try {
+ lastOrganizer.addStartingWindow(info, activity.token);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onTaskStart callback", e);
return false;
}
- final TaskOrganizerState state =
- mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
- state.addStartingWindow(task, activity, launchTheme, taskSnapshot);
return true;
}
void removeStartingWindow(Task task, boolean prepareAnimation) {
final Task rootTask = task.getRootTask();
- if (rootTask == null || rootTask.mTaskOrganizer == null) {
+ if (rootTask == null) {
+ return;
+ }
+ final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ if (lastOrganizer == null) {
return;
}
- final TaskOrganizerState state =
- mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
- state.removeStartingWindow(task, prepareAnimation);
+ final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
+ removalInfo.taskId = task.mTaskId;
+ removalInfo.playRevealAnimation = prepareAnimation;
+ final boolean playShiftUpAnimation = !task.inMultiWindowMode();
+ final ActivityRecord topActivity = task.topActivityContainsStartingWindow();
+ if (topActivity != null) {
+ removalInfo.deferRemoveForIme = topActivity.mDisplayContent
+ .mayImeShowOnLaunchingActivity(topActivity);
+ if (prepareAnimation && playShiftUpAnimation) {
+ final WindowState mainWindow =
+ topActivity.findMainWindow(false/* includeStartingApp */);
+ if (mainWindow != null) {
+ final SurfaceControl.Transaction t = mainWindow.getPendingTransaction();
+ removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
+ removalInfo.mainFrame = mainWindow.getRelativeFrame();
+ t.setPosition(removalInfo.windowAnimationLeash,
+ removalInfo.mainFrame.left, removalInfo.mainFrame.top);
+ }
+ }
+ }
+ try {
+ lastOrganizer.removeStartingWindow(removalInfo);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onStartTaskFinished callback", e);
+ }
}
boolean copySplashScreenView(Task task) {
final Task rootTask = task.getRootTask();
- if (rootTask == null || rootTask.mTaskOrganizer == null) {
+ if (rootTask == null) {
+ return false;
+ }
+ final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ if (lastOrganizer == null) {
+ return false;
+ }
+ try {
+ lastOrganizer.copySplashScreenView(task.mTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending copyStartingWindowView callback", e);
return false;
}
- final TaskOrganizerState state =
- mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
- state.copySplashScreenView(task);
return true;
}
@@ -588,12 +559,18 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
*/
public void onAppSplashScreenViewRemoved(Task task) {
final Task rootTask = task.getRootTask();
- if (rootTask == null || rootTask.mTaskOrganizer == null) {
+ if (rootTask == null) {
return;
}
- final TaskOrganizerState state =
- mTaskOrganizerStates.get(rootTask.mTaskOrganizer.asBinder());
- state.onAppSplashScreenViewRemoved(task);
+ final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ if (lastOrganizer == null) {
+ return;
+ }
+ try {
+ lastOrganizer.onAppSplashScreenViewRemoved(task.mTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onAppSplashScreenViewRemoved callback", e);
+ }
}
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
@@ -688,7 +665,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Delete root task display=%d winMode=%d",
task.getDisplayId(), task.getWindowingMode());
- task.removeImmediately("deleteRootTask");
+ task.remove(true /* withTransition */, "deleteRootTask");
return true;
}
} finally {
@@ -718,6 +695,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
if (state != null) {
state.mOrganizer.onTaskVanished(task);
}
+ mLastSentTaskInfos.remove(task);
break;
case PendingTaskEvent.EVENT_INFO_CHANGED:
dispatchTaskInfoChanged(event.mTask, event.mForce);
@@ -777,18 +755,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mTmpTaskInfo.configuration.unset();
task.fillTaskInfo(mTmpTaskInfo);
- boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo);
- if (!changed) {
- int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
- final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
- ? (int) mTmpTaskInfo.configuration.windowConfiguration.diff(
- lastInfo.configuration.windowConfiguration,
- true /* compareUndefined */) : 0;
- if ((winCfgChanges & REPORT_WINDOW_CONFIGS) == 0) {
- cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
- }
- changed = (cfgChanges & REPORT_CONFIGS) != 0;
- }
+ boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo)
+ || !configurationsAreEqualForOrganizer(
+ mTmpTaskInfo.configuration, lastInfo.configuration);
if (!(changed || force)) {
// mTmpTaskInfo will be reused next time.
return;
@@ -1021,9 +990,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
for (int k = 0; k < tasks.size(); k++) {
final Task task = tasks.get(k);
final int mode = task.getWindowingMode();
- if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, mode)) {
- continue;
- }
pw.println(innerPrefix + " ("
+ WindowConfiguration.windowingModeToString(mode) + ") " + task);
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index e74371036619..141d889c1804 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -54,6 +54,7 @@ import com.android.server.wm.utils.InsetUtils;
import com.google.android.collect.Sets;
import java.io.PrintWriter;
+import java.util.Set;
/**
* When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and
@@ -167,7 +168,10 @@ class TaskSnapshotController {
* calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot.
*/
@VisibleForTesting
- void addSkipClosingAppSnapshotTasks(ArraySet<Task> tasks) {
+ void addSkipClosingAppSnapshotTasks(Set<Task> tasks) {
+ if (shouldDisableSnapshots()) {
+ return;
+ }
mSkipClosingAppSnapshotTasks.addAll(tasks);
}
@@ -175,46 +179,49 @@ class TaskSnapshotController {
snapshotTasks(tasks, false /* allowSnapshotHome */);
}
- private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
- for (int i = tasks.size() - 1; i >= 0; i--) {
- final Task task = tasks.valueAt(i);
- final TaskSnapshot snapshot;
- final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
- if (snapshotHome) {
- snapshot = snapshotTask(task);
- } else {
- switch (getSnapshotMode(task)) {
- case SNAPSHOT_MODE_NONE:
- continue;
- case SNAPSHOT_MODE_APP_THEME:
- snapshot = drawAppThemeSnapshot(task);
- break;
- case SNAPSHOT_MODE_REAL:
- snapshot = snapshotTask(task);
- break;
- default:
- snapshot = null;
- break;
- }
+ void recordTaskSnapshot(Task task, boolean allowSnapshotHome) {
+ final TaskSnapshot snapshot;
+ final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
+ if (snapshotHome) {
+ snapshot = snapshotTask(task);
+ } else {
+ switch (getSnapshotMode(task)) {
+ case SNAPSHOT_MODE_NONE:
+ return;
+ case SNAPSHOT_MODE_APP_THEME:
+ snapshot = drawAppThemeSnapshot(task);
+ break;
+ case SNAPSHOT_MODE_REAL:
+ snapshot = snapshotTask(task);
+ break;
+ default:
+ snapshot = null;
+ break;
}
- if (snapshot != null) {
- final HardwareBuffer buffer = snapshot.getHardwareBuffer();
- if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
- buffer.close();
- Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
- + buffer.getHeight());
- } else {
- mCache.putSnapshot(task, snapshot);
- // Don't persist or notify the change for the temporal snapshot.
- if (!snapshotHome) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- task.onSnapshotChanged(snapshot);
- }
+ }
+ if (snapshot != null) {
+ final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+ if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+ buffer.close();
+ Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+ + buffer.getHeight());
+ } else {
+ mCache.putSnapshot(task, snapshot);
+ // Don't persist or notify the change for the temporal snapshot.
+ if (!snapshotHome) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ task.onSnapshotChanged(snapshot);
}
}
}
}
+ private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
+ for (int i = tasks.size() - 1; i >= 0; i--) {
+ recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome);
+ }
+ }
+
/**
* Retrieves a snapshot. If {@param restoreFromDisk} equals {@code true}, DO NOT HOLD THE WINDOW
* MANAGER LOCK WHEN CALLING THIS METHOD!
@@ -283,11 +290,13 @@ class TaskSnapshotController {
final WindowState mainWindow = result.second;
final Rect contentInsets = getSystemBarInsets(task.getBounds(),
mainWindow.getInsetsStateWithVisibilityOverride());
- InsetUtils.addInsets(contentInsets, activity.getLetterboxInsets());
+ final Rect letterboxInsets = activity.getLetterboxInsets();
+ InsetUtils.addInsets(contentInsets, letterboxInsets);
builder.setIsRealSnapshot(true);
builder.setId(System.currentTimeMillis());
builder.setContentInsets(contentInsets);
+ builder.setLetterboxInsets(letterboxInsets);
final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
final boolean isShowWallpaper = mainWindow.hasWallpaper();
@@ -568,7 +577,8 @@ class TaskSnapshotController {
return null;
}
final Rect contentInsets = new Rect(systemBarInsets);
- InsetUtils.addInsets(contentInsets, topChild.getLetterboxInsets());
+ final Rect letterboxInsets = topChild.getLetterboxInsets();
+ InsetUtils.addInsets(contentInsets, letterboxInsets);
// Note, the app theme snapshot is never translucent because we enforce a non-translucent
// color above
@@ -577,9 +587,9 @@ class TaskSnapshotController {
topChild.mActivityComponent, hwBitmap.getHardwareBuffer(),
hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
- contentInsets, false /* isLowResolution */, false /* isRealSnapshot */,
- task.getWindowingMode(), getAppearance(task), false /* isTranslucent */,
- false /* hasImeSurface */);
+ contentInsets, letterboxInsets, false /* isLowResolution */,
+ false /* isRealSnapshot */, task.getWindowingMode(),
+ getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */);
}
/**
@@ -683,7 +693,8 @@ class TaskSnapshotController {
}
static Rect getSystemBarInsets(Rect frame, InsetsState state) {
- return state.calculateInsets(frame, Type.systemBars(), false /* ignoreVisibility */);
+ return state.calculateInsets(
+ frame, Type.systemBars(), false /* ignoreVisibility */).toRect();
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
index d3bfbab8106f..9189e51f860c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
@@ -20,7 +20,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.app.ActivityManager;
-import android.window.TaskSnapshot;
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
@@ -30,6 +29,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.util.Slog;
+import android.window.TaskSnapshot;
import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
@@ -196,6 +196,8 @@ class TaskSnapshotLoader {
return new TaskSnapshot(proto.id, topActivityComponent, buffer,
hwBitmap.getColorSpace(), proto.orientation, proto.rotation, taskSize,
new Rect(proto.insetLeft, proto.insetTop, proto.insetRight, proto.insetBottom),
+ new Rect(proto.letterboxInsetLeft, proto.letterboxInsetTop,
+ proto.letterboxInsetRight, proto.letterboxInsetBottom),
loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
} catch (IOException e) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 60c4766ea18f..9fbcd7cc5d84 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -23,7 +23,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.window.TaskSnapshot;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Process;
@@ -31,6 +30,7 @@ import android.os.SystemClock;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.window.TaskSnapshot;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -380,6 +380,10 @@ class TaskSnapshotPersister {
proto.insetTop = mSnapshot.getContentInsets().top;
proto.insetRight = mSnapshot.getContentInsets().right;
proto.insetBottom = mSnapshot.getContentInsets().bottom;
+ proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left;
+ proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top;
+ proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right;
+ proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom;
proto.isRealSnapshot = mSnapshot.isRealSnapshot();
proto.windowingMode = mSnapshot.getWindowingMode();
proto.appearance = mSnapshot.getAppearance();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index cc4abab01b72..059eb876ad94 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -72,6 +72,7 @@ import android.util.Slog;
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
@@ -166,6 +167,7 @@ class TaskSnapshotSurface implements StartingSurface {
final ClientWindowFrames tmpFrames = new ClientWindowFrames();
final Rect taskBounds;
final InsetsState mTmpInsetsState = new InsetsState();
+ final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
final TaskDescription taskDescription = new TaskDescription();
@@ -227,7 +229,8 @@ class TaskSnapshotSurface implements StartingSurface {
int displayId = activity.getDisplayContent().getDisplayId();
try {
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- mTmpInsetsState, null /* outInputChannel */, mTmpInsetsState, mTempControls);
+ mRequestedVisibilities, null /* outInputChannel */, mTmpInsetsState,
+ mTempControls);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
return null;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 0cd098070401..4db8ef49a11a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -16,11 +16,19 @@
package com.android.server.wm;
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
@@ -31,34 +39,45 @@ import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TransitionFlags;
+import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.view.animation.Animation;
-import android.window.IRemoteTransition;
+import android.window.RemoteTransition;
import android.window.TransitionInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.function.pooled.PooledLambda;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -100,12 +119,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
@Retention(RetentionPolicy.SOURCE)
@interface TransitionState {}
- final @WindowManager.TransitionType int mType;
+ final @TransitionType int mType;
private int mSyncId;
- private @WindowManager.TransitionFlags int mFlags;
+ private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
- private IRemoteTransition mRemoteTransition = null;
+ private RemoteTransition mRemoteTransition = null;
/** Only use for clean-up after binder death! */
private SurfaceControl.Transaction mStartTransaction = null;
@@ -124,16 +143,53 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
/** The final animation targets derived from participants after promotion. */
private ArraySet<WindowContainer> mTargets = null;
+ /**
+ * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
+ * the transition animation.
+ */
+ private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
+
+ /** Set of transient activities (lifecycle initially tied to this transition). */
+ private ArraySet<ActivityRecord> mTransientLaunches = null;
+
+ /** Custom activity-level animation options and callbacks. */
+ private TransitionInfo.AnimationOptions mOverrideOptions;
+ private IRemoteCallback mClientAnimationStartCallback = null;
+ private IRemoteCallback mClientAnimationFinishCallback = null;
+
private @TransitionState int mState = STATE_COLLECTING;
- private boolean mReadyCalled = false;
+ private final ReadyTracker mReadyTracker = new ReadyTracker();
+
+ // TODO(b/188595497): remove when not needed.
+ /** @see RecentsAnimationController#mNavigationBarAttachedToApp */
+ private boolean mNavBarAttachedToApp = false;
+ private int mRecentsDisplayId = INVALID_DISPLAY;
- Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags,
+ Transition(@TransitionType int type, @TransitionFlags int flags, long timeoutMs,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
- mSyncId = mSyncEngine.startSyncSet(this);
+ mSyncId = mSyncEngine.startSyncSet(this, timeoutMs);
+ }
+
+ void addFlag(int flag) {
+ mFlags |= flag;
+ }
+
+ /** Records an activity as transient-launch. This activity must be already collected. */
+ void setTransientLaunch(@NonNull ActivityRecord activity) {
+ if (mTransientLaunches == null) {
+ mTransientLaunches = new ArraySet<>();
+ }
+ mTransientLaunches.add(activity);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+ + "transient-launch", mSyncId, activity);
+ }
+
+ boolean isTransientLaunch(@NonNull ActivityRecord activity) {
+ return mTransientLaunches != null && mTransientLaunches.contains(activity);
}
@VisibleForTesting
@@ -141,6 +197,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mSyncId;
}
+ @TransitionFlags
+ int getFlags() {
+ return mFlags;
+ }
+
/**
* Formally starts the transition. Participants can be collected before this is started,
* but this won't consider itself ready until started -- even if all the participants have
@@ -153,9 +214,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mState = STATE_STARTED;
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
mSyncId);
- if (mReadyCalled) {
- setReady();
- }
+ applyReady();
}
/**
@@ -170,6 +229,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr);
curr = curr.getParent()) {
mChanges.put(curr, new ChangeInfo(curr));
+ if (isReadyGroup(curr)) {
+ mReadyTracker.addGroup(curr);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
+ + " Transition %d with root=%s", mSyncId, curr);
+ }
}
if (mParticipants.contains(wc)) return;
mSyncEngine.addToSyncSet(mSyncId, wc);
@@ -207,25 +271,76 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
/**
+ * Specifies configuration change explicitly for the window container, so it can be chosen as
+ * transition target. This is usually used with transition mode
+ * {@link android.view.WindowManager#TRANSIT_CHANGE}.
+ */
+ void setKnownConfigChanges(WindowContainer<?> wc, @ActivityInfo.Config int changes) {
+ final ChangeInfo changeInfo = mChanges.get(wc);
+ if (changeInfo != null) {
+ changeInfo.mKnownConfigChanges = changes;
+ }
+ }
+
+ private void sendRemoteCallback(@Nullable IRemoteCallback callback) {
+ if (callback == null) return;
+ mController.mAtm.mH.sendMessage(PooledLambda.obtainMessage(cb -> {
+ try {
+ cb.sendResult(null);
+ } catch (RemoteException e) { }
+ }, callback));
+ }
+
+ /**
+ * Set animation options for collecting transition by ActivityRecord.
+ * @param options AnimationOptions captured from ActivityOptions
+ */
+ void setOverrideAnimation(TransitionInfo.AnimationOptions options,
+ @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
+ if (mSyncId < 0) return;
+ mOverrideOptions = options;
+ sendRemoteCallback(mClientAnimationStartCallback);
+ mClientAnimationStartCallback = startCallback;
+ mClientAnimationFinishCallback = finishCallback;
+ }
+
+ /**
* Call this when all known changes related to this transition have been applied. Until
* all participants have finished drawing, the transition can still collect participants.
*
* If this is called before the transition is started, it will be deferred until start.
+ *
+ * @param wc A reference point to determine which ready-group to update. For now, each display
+ * has its own ready-group, so this is used to look-up which display to mark ready.
+ * The transition will wait for all groups to be ready.
*/
- void setReady(boolean ready) {
+ void setReady(WindowContainer wc, boolean ready) {
if (mSyncId < 0) return;
- if (mState < STATE_STARTED) {
- mReadyCalled = ready;
- return;
- }
+ mReadyTracker.setReadyFrom(wc, ready);
+ applyReady();
+ }
+
+ private void applyReady() {
+ if (mState < STATE_STARTED) return;
+ final boolean ready = mReadyTracker.allReady();
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Set transition ready=%b %d", ready, mSyncId);
mSyncEngine.setReady(mSyncId, ready);
}
- /** @see #setReady . This calls with parameter true. */
- void setReady() {
- setReady(true);
+ /**
+ * Sets all possible ready groups to ready.
+ * @see ReadyTracker#setAllReady.
+ */
+ void setAllReady() {
+ if (mSyncId < 0) return;
+ mReadyTracker.setAllReady();
+ applyReady();
+ }
+
+ @VisibleForTesting
+ boolean allReady() {
+ return mReadyTracker.allReady();
}
/**
@@ -275,20 +390,82 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
// Commit all going-invisible containers
+ boolean activitiesWentInvisible = false;
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar != null && !ar.isVisibleRequested()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Commit activity becoming invisible: %s", ar);
- ar.commitVisibility(false /* visible */, false /* performLayout */);
+ if (ar != null) {
+ boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
+ // We need both the expected visibility AND current requested-visibility to be
+ // false. If it is expected-visible but not currently visible, it means that
+ // another animation is queued-up to animate this to invisibility, so we can't
+ // remove the surfaces yet. If it is currently visible, but not expected-visible,
+ // then doing commitVisibility here would actually be out-of-order and leave the
+ // activity in a bad state.
+ if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
+ boolean commitVisibility = true;
+ if (ar.getDeferHidingClient() && ar.getTask() != null) {
+ if (ar.pictureInPictureArgs != null
+ && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
+ mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs);
+ // Avoid commit visibility to false here, or else we will get a sudden
+ // "flash" / surface going invisible for a split second.
+ commitVisibility = false;
+ } else {
+ mController.mAtm.mTaskSupervisor.mUserLeaving = true;
+ ar.getTaskFragment().startPausing(false /* uiSleeping */,
+ null /* resuming */, "finishTransition");
+ mController.mAtm.mTaskSupervisor.mUserLeaving = false;
+ }
+ }
+ if (commitVisibility) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit activity becoming invisible: %s", ar);
+ final Task task = ar.getTask();
+ if (task != null && !task.isVisibleRequested()
+ && mTransientLaunches != null) {
+ // If transition is transient, then snapshots are taken at end of
+ // transition.
+ mController.mTaskSnapshotController.recordTaskSnapshot(
+ task, false /* allowSnapshotHome */);
+ }
+ ar.commitVisibility(false /* visible */, false /* performLayout */,
+ true /* fromTransition */);
+ activitiesWentInvisible = true;
+ }
+ }
+ if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
+ // Legacy dispatch relies on this (for now).
+ ar.mEnteringAnimation = visibleAtTransitionEnd;
+ }
+ mController.dispatchLegacyAppTransitionFinished(ar);
}
final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
- if (wt != null && !wt.isVisibleRequested()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- " Commit wallpaper becoming invisible: %s", ar);
- wt.commitVisibility(false /* visible */);
+ if (wt != null) {
+ final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
+ if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Commit wallpaper becoming invisible: %s", wt);
+ wt.commitVisibility(false /* visible */);
+ }
}
}
+ if (activitiesWentInvisible) {
+ // Always schedule stop processing when transition finishes because activities don't
+ // stop while they are in a transition thus their stop could still be pending.
+ mController.mAtm.mTaskSupervisor
+ .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
+ }
+
+ sendRemoteCallback(mClientAnimationFinishCallback);
+
+ legacyRestoreNavigationBarFromApp();
+
+ if (mRecentsDisplayId != INVALID_DISPLAY) {
+ // Clean up input monitors (for recents)
+ final DisplayContent dc =
+ mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
+ dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
+ }
}
void abort() {
@@ -297,16 +474,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (mState != STATE_COLLECTING) {
throw new IllegalStateException("Too late to abort.");
}
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
+ mController.dispatchLegacyAppTransitionCancelled();
mState = STATE_ABORT;
// Syncengine abort will call through to onTransactionReady()
mSyncEngine.abort(mSyncId);
}
- void setRemoteTransition(IRemoteTransition remoteTransition) {
+ void setRemoteTransition(RemoteTransition remoteTransition) {
mRemoteTransition = remoteTransition;
}
- IRemoteTransition getRemoteTransition() {
+ RemoteTransition getRemoteTransition() {
return mRemoteTransition;
}
@@ -327,6 +506,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mController.mAtm.mRootWindowContainer.getDisplayContent(displayId)
.getPendingTransaction().merge(transaction);
mSyncId = -1;
+ mOverrideOptions = null;
return;
}
@@ -340,9 +520,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
+ info.setAnimationOptions(mOverrideOptions);
+
+ // TODO(b/188669821): Move to animation impl in shell.
+ handleLegacyRecentsStartBehavior(displayId, info);
handleNonAppWindowsInTransition(displayId, mType, mFlags);
+ reportStartReasonsToLogger();
+
+ // The callback is only populated for custom activity-level client animations
+ sendRemoteCallback(mClientAnimationStartCallback);
+
// Manually show any activities that are visibleRequested. This is needed to properly
// support simultaneous animation queueing/merging. Specifically, if transition A makes
// an activity invisible, it's finishTransaction (which is applied *after* the animation)
@@ -354,12 +543,54 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar == null || !ar.mVisibleRequested) continue;
transaction.show(ar.getSurfaceControl());
+
+ // Also manually show any non-reported parents. This is necessary in a few cases
+ // where a task is NOT organized but had its visibility changed within its direct
+ // parent. An example of this is if an alternate home leaf-task HB is started atop the
+ // normal home leaf-task HA: these are both in the Home root-task HR, so there will be a
+ // transition containing HA and HB where HA surface is hidden. If a standard task SA is
+ // launched on top, then HB finishes, no transition will happen since neither home is
+ // visible. When SA finishes, the transition contains HR rather than HA. Since home
+ // leaf-tasks are NOT organized, HA won't be in the transition and thus its surface
+ // wouldn't be shown. Just show is safe here since all other properties will have
+ // already been reset by the original hiding-transition's finishTransaction (we can't
+ // show in the finishTransaction because by then the activity doesn't hide until
+ // surface placement).
+ for (WindowContainer p = ar.getParent(); p != null && !mTargets.contains(p);
+ p = p.getParent()) {
+ if (p.getSurfaceControl() != null) {
+ transaction.show(p.getSurfaceControl());
+ }
+ }
+ }
+
+ // Record windowtokens (activity/wallpaper) that are expected to be visible after the
+ // transition animation. This will be used in finishTransition to prevent prematurely
+ // committing visibility.
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mParticipants.valueAt(i);
+ if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
+ mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
+ }
+
+ // Take task snapshots before the animation so that we can capture IME before it gets
+ // transferred. If transition is transient, IME won't be moved during the transition and
+ // the tasks are still live, so we take the snapshot at the end of the transition instead.
+ if (mTransientLaunches == null) {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ if (ar == null || ar.isVisibleRequested() || ar.getTask() == null
+ || ar.getTask().isVisibleRequested()) continue;
+ mController.mTaskSnapshotController.recordTaskSnapshot(
+ ar.getTask(), false /* allowSnapshotHome */);
+ }
}
mStartTransaction = transaction;
mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
if (mController.getTransitionPlayer() != null) {
+ mController.dispatchLegacyAppTransitionStarting(info);
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
@@ -375,6 +606,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
cleanUpOnFailure();
}
mSyncId = -1;
+ mOverrideOptions = null;
}
/**
@@ -391,17 +623,152 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- finishTransition();
+ mController.finishTransition(this);
+ }
+
+ /** @see RecentsAnimationController#attachNavigationBarToApp */
+ private void handleLegacyRecentsStartBehavior(int displayId, TransitionInfo info) {
+ if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
+ return;
+ }
+ final DisplayContent dc =
+ mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
+ if (dc == null) return;
+ mRecentsDisplayId = displayId;
+
+ // Recents has an input-consumer to grab input from the "live tile" app. Set that up here
+ final InputConsumerImpl recentsAnimationInputConsumer =
+ dc.getInputMonitor().getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
+ if (recentsAnimationInputConsumer != null) {
+ // find the top-most going-away activity and the recents activity. The top-most
+ // is used as layer reference while the recents is used for registering the consumer
+ // override.
+ ActivityRecord recentsActivity = null;
+ ActivityRecord topActivity = null;
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.getTaskInfo() == null) continue;
+ final Task task = Task.fromWindowContainerToken(
+ info.getChanges().get(i).getTaskInfo().token);
+ if (task == null) continue;
+ final int activityType = change.getTaskInfo().topActivityType;
+ final boolean isRecents = activityType == ACTIVITY_TYPE_HOME
+ || activityType == ACTIVITY_TYPE_RECENTS;
+ if (isRecents && recentsActivity == null) {
+ recentsActivity = task.getTopVisibleActivity();
+ } else if (!isRecents && topActivity == null) {
+ topActivity = task.getTopNonFinishingActivity();
+ }
+ }
+ if (recentsActivity != null && topActivity != null) {
+ recentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(
+ topActivity.getBounds());
+ dc.getInputMonitor().setActiveRecents(recentsActivity, topActivity);
+ }
+ }
+
+ // The rest of this function handles nav-bar reparenting
+
+ if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
+ // Skip the case where the nav bar is controlled by fade rotation.
+ || dc.getFadeRotationAnimationController() != null) {
+ return;
+ }
+
+ WindowContainer topWC = null;
+ // Find the top-most non-home, closing app.
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change c = info.getChanges().get(i);
+ if (c.getTaskInfo() == null || c.getTaskInfo().displayId != displayId
+ || c.getTaskInfo().getActivityType() != ACTIVITY_TYPE_STANDARD
+ || !(c.getMode() == TRANSIT_CLOSE || c.getMode() == TRANSIT_TO_BACK)) {
+ continue;
+ }
+ topWC = WindowContainer.fromBinder(c.getContainer().asBinder());
+ break;
+ }
+ if (topWC == null || topWC.inMultiWindowMode()) {
+ return;
+ }
+
+ final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
+ if (navWindow == null || navWindow.mToken == null) {
+ return;
+ }
+ mNavBarAttachedToApp = true;
+ navWindow.mToken.cancelAnimation();
+ final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction();
+ final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl();
+ t.reparent(navSurfaceControl, topWC.getSurfaceControl());
+ t.show(navSurfaceControl);
+
+ final WindowContainer imeContainer = dc.getImeContainer();
+ if (imeContainer.isVisible()) {
+ t.setRelativeLayer(navSurfaceControl, imeContainer.getSurfaceControl(), 1);
+ } else {
+ // Place the nav bar on top of anything else in the top activity.
+ t.setLayer(navSurfaceControl, Integer.MAX_VALUE);
+ }
+ if (mController.mStatusBar != null) {
+ mController.mStatusBar.setNavigationBarLumaSamplingEnabled(displayId, false);
+ }
+ }
+
+ /** @see RecentsAnimationController#restoreNavigationBarFromApp */
+ void legacyRestoreNavigationBarFromApp() {
+ if (!mNavBarAttachedToApp) return;
+ mNavBarAttachedToApp = false;
+
+ if (mRecentsDisplayId == INVALID_DISPLAY) {
+ Slog.e(TAG, "Reparented navigation bar without a valid display");
+ mRecentsDisplayId = DEFAULT_DISPLAY;
+ }
+
+ if (mController.mStatusBar != null) {
+ mController.mStatusBar.setNavigationBarLumaSamplingEnabled(mRecentsDisplayId, true);
+ }
+
+ final DisplayContent dc =
+ mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
+ final WindowState navWindow = dc.getDisplayPolicy().getNavigationBar();
+ if (navWindow == null) return;
+ navWindow.setSurfaceTranslationY(0);
+
+ final WindowToken navToken = navWindow.mToken;
+ if (navToken == null) return;
+ final SurfaceControl.Transaction t = dc.getPendingTransaction();
+ final WindowContainer parent = navToken.getParent();
+ t.setLayer(navToken.getSurfaceControl(), navToken.getLastLayer());
+
+ boolean animate = false;
+ // Search for the home task. If it is supposed to be visible, then the navbar is not at
+ // the bottom of the screen, so we need to animate it.
+ for (int i = 0; i < mTargets.size(); ++i) {
+ final Task task = mTargets.valueAt(i).asTask();
+ if (task == null || !task.isHomeOrRecentsRootTask()) continue;
+ animate = task.isVisibleRequested();
+ break;
+ }
+
+ if (animate) {
+ final NavBarFadeAnimationController controller =
+ new NavBarFadeAnimationController(dc);
+ controller.fadeWindowToken(true);
+ } else {
+ // Reparent the SurfaceControl of nav bar token back.
+ t.reparent(navToken.getSurfaceControl(), parent.getSurfaceControl());
+ }
}
private void handleNonAppWindowsInTransition(int displayId,
- @WindowManager.TransitionType int transit, int flags) {
+ @TransitionType int transit, @TransitionFlags int flags) {
final DisplayContent dc =
mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
if (dc == null) {
return;
}
- if (transit == TRANSIT_KEYGUARD_GOING_AWAY
+ if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
+ || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
&& !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
&& (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
@@ -419,12 +786,35 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
(flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
(flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
(flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
- mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
- SystemClock.uptimeMillis(), 0 /* duration */);
+ if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
+ // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
+ // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
+ // need to call IKeyguardService#keyguardGoingAway here.
+ mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
+ SystemClock.uptimeMillis(), 0 /* duration */);
+ }
}
if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
- mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange();
+ mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
+ true /* keyguardOccludingStarted */);
+ }
+ }
+
+ private void reportStartReasonsToLogger() {
+ // Record transition start in metrics logger. We just assume everything is "DRAWN"
+ // at this point since splash-screen is a presentation (shell) detail.
+ ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
+ if (r == null || !r.mVisibleRequested) continue;
+ // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
+ // ready due to starting-window.
+ reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData
+ && !r.mLastAllReadyAtSync)
+ ? APP_TRANSITION_SPLASH_SCREEN : APP_TRANSITION_WINDOWS_DRAWN);
}
+ mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
+ reasons);
}
@Override
@@ -465,6 +855,22 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return wc.asWallpaperToken() != null;
}
+ private static boolean occludesKeyguard(WindowContainer wc) {
+ final ActivityRecord ar = wc.asActivityRecord();
+ if (ar != null) {
+ return ar.canShowWhenLocked();
+ }
+ final Task t = wc.asTask();
+ if (t != null) {
+ // Get the top activity which was visible (since this is going away, it will remain
+ // client visible until the transition is finished).
+ // skip hidden (or about to hide) apps
+ final ActivityRecord top = t.getActivity(WindowToken::isClientVisible);
+ return top != null && top.canShowWhenLocked();
+ }
+ return false;
+ }
+
/**
* Under some conditions (eg. all visible targets within a parent container are transitioning
* the same way) the transition can be "promoted" to the parent container. This means an
@@ -612,7 +1018,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// of participants that should always be reported even if they aren't top.
for (WindowContainer wc : participants) {
// Don't include detached windows.
- if (!wc.isAttached()) continue;
+ if (!wc.isAttached()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ " Rejecting as detached: %s", wc);
+ continue;
+ }
final ChangeInfo changeInfo = changes.get(wc);
@@ -629,15 +1039,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (reportIfNotTop(wc)) {
tmpList.add(wc);
}
+ // Wallpaper must be the top (regardless of how nested it is in DisplayAreas).
+ boolean skipIntermediateReports = isWallpaper(wc);
for (WindowContainer p = wc.getParent(); p != null; p = p.getParent()) {
- if (!p.isAttached() || !changes.get(p).hasChanged(p)) {
+ if (!p.isAttached() || changes.get(p) == null || !changes.get(p).hasChanged(p)) {
// Again, we're skipping no-ops
break;
}
if (participants.contains(p)) {
topParent = p;
break;
- } else if (reportIfNotTop(p)) {
+ } else if (isWallpaper(p)) {
+ skipIntermediateReports = true;
+ } else if (reportIfNotTop(p) && !skipIntermediateReports) {
tmpList.add(p);
}
}
@@ -707,12 +1121,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
/**
+ * A ready group is defined by a root window-container where all transitioning windows under
+ * it are expected to animate together as a group. At the moment, this treats each display as
+ * a ready-group to match the existing legacy transition behavior.
+ */
+ private static boolean isReadyGroup(WindowContainer wc) {
+ return wc instanceof DisplayContent;
+ }
+
+ /**
* Construct a TransitionInfo object from a set of targets and changes. Also populates the
* root surface.
*/
@VisibleForTesting
@NonNull
- static TransitionInfo calculateTransitionInfo(int type, int flags,
+ static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
final TransitionInfo out = new TransitionInfo(type, flags);
@@ -723,17 +1146,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
// Find the top-most shared ancestor of app targets
- WindowContainer ancestor = null;
- for (int i = appTargets.size() - 1; i >= 0; --i) {
- final WindowContainer wc = appTargets.valueAt(i);
- ancestor = wc;
- break;
- }
- if (ancestor == null) {
+ if (appTargets.isEmpty()) {
out.setRootLeash(new SurfaceControl(), 0, 0);
return out;
}
- ancestor = ancestor.getParent();
+ WindowContainer ancestor = appTargets.valueAt(appTargets.size() - 1).getParent();
// Go up ancestor parent chain until all targets are descendants.
ancestorLoop:
@@ -798,6 +1215,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
task.fillTaskInfo(tinfo);
change.setTaskInfo(tinfo);
+ change.setRotationAnimation(getTaskRotationAnimation(task));
+ final ActivityRecord topMostActivity = task.getTopMostActivity();
+ change.setAllowEnterPip(topMostActivity != null
+ && topMostActivity.checkEnterPictureInPictureAppOpsState());
}
out.addChange(change);
}
@@ -805,6 +1226,27 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return out;
}
+ private static int getTaskRotationAnimation(@NonNull Task task) {
+ final ActivityRecord top = task.getTopVisibleActivity();
+ if (top == null) return ROTATION_ANIMATION_UNSPECIFIED;
+ final WindowState mainWin = top.findMainWindow(false);
+ if (mainWin == null) return ROTATION_ANIMATION_UNSPECIFIED;
+ int anim = mainWin.getRotationAnimationHint();
+ if (anim >= 0) return anim;
+ anim = mainWin.getAttrs().rotationAnimation;
+ if (anim != ROTATION_ANIMATION_SEAMLESS) return anim;
+ if (mainWin != task.mDisplayContent.getDisplayPolicy().getTopFullscreenOpaqueWindow()
+ || !top.matchParentBounds()) {
+ // At the moment, we only support seamless rotation if there is only one window showing.
+ return ROTATION_ANIMATION_UNSPECIFIED;
+ }
+ return mainWin.getAttrs().rotationAnimation;
+ }
+
+ boolean getLegacyIsReady() {
+ return mState == STATE_STARTED && mSyncId >= 0 && mSyncEngine.isReady(mSyncId);
+ }
+
static Transition fromBinder(IBinder binder) {
return (Transition) binder;
}
@@ -823,6 +1265,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final Rect mAbsoluteBounds = new Rect();
boolean mShowWallpaper;
int mRotation = ROTATION_UNDEFINED;
+ @ActivityInfo.Config int mKnownConfigChanges;
ChangeInfo(@NonNull WindowContainer origState) {
mVisible = origState.isVisibleRequested();
@@ -845,6 +1288,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final boolean currVisible = newState.isVisibleRequested();
if (currVisible == mVisible && !mVisible) return false;
return currVisible != mVisible
+ || mKnownConfigChanges != 0
// if mWindowingMode is 0, this container wasn't attached at collect time, so
// assume no change in windowing-mode.
|| (mWindowingMode != 0 && newState.getWindowingMode() != mWindowingMode)
@@ -891,9 +1335,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
flags |= FLAG_IS_VOICE_INTERACTION;
}
}
+ final DisplayContent dc = wc.asDisplayContent();
+ if (dc != null) {
+ flags |= FLAG_IS_DISPLAY;
+ if (dc.hasAlertWindowSurfaces()) {
+ flags |= FLAG_DISPLAY_HAS_ALERT_WINDOWS;
+ }
+ }
if (isWallpaper(wc)) {
flags |= FLAG_IS_WALLPAPER;
}
+ if (occludesKeyguard(wc)) {
+ flags |= FLAG_OCCLUDES_KEYGUARD;
+ }
return flags;
}
@@ -910,4 +1364,95 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mChildren.addAll(wcs);
}
}
+
+ /**
+ * The transition sync mechanism has 2 parts:
+ * 1. Whether all WM operations for a particular transition are "ready" (eg. did the app
+ * launch or stop or get a new configuration?).
+ * 2. Whether all the windows involved have finished drawing their final-state content.
+ *
+ * A transition animation can play once both parts are complete. This ready-tracker keeps track
+ * of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
+ * even if the WM operations in one group are ready, the whole transition itself may not be
+ * ready if there are WM operations still pending in another group. This class helps keep track
+ * of readiness across the multiple groups. Currently, we assume that each display is a group
+ * since that is how it has been until now.
+ */
+ private static class ReadyTracker {
+ private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
+
+ /**
+ * Ensures that this doesn't report as allReady before it has been used. This is needed
+ * in very niche cases where a transition is a no-op (nothing has been collected) but we
+ * still want to be marked ready (via. setAllReady).
+ */
+ private boolean mUsed = false;
+
+ /**
+ * If true, this overrides all ready groups and reports ready. Used by shell-initiated
+ * transitions via {@link #setAllReady()}.
+ */
+ private boolean mReadyOverride = false;
+
+ /**
+ * Adds a ready-group. Any setReady calls in this subtree will be tracked together. For
+ * now these are only DisplayContents.
+ */
+ void addGroup(WindowContainer wc) {
+ if (mReadyGroups.containsKey(wc)) {
+ Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
+ return;
+ }
+ mReadyGroups.put(wc, false);
+ }
+
+ /**
+ * Sets a group's ready state.
+ * @param wc Any container within a group's subtree. Used to identify the ready-group.
+ */
+ void setReadyFrom(WindowContainer wc, boolean ready) {
+ mUsed = true;
+ WindowContainer current = wc;
+ 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);
+ break;
+ }
+ current = current.getParent();
+ }
+ }
+
+ /** Marks this as ready regardless of individual groups. */
+ void setAllReady() {
+ ProtoLog.v(ProtoLogGroup.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, " allReady query: used=%b "
+ + "override=%b states=[%s]", mUsed, mReadyOverride, groupsToString());
+ if (!mUsed) return false;
+ if (mReadyOverride) return true;
+ for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mReadyGroups.keyAt(i);
+ if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
+ if (!mReadyGroups.valueAt(i)) return false;
+ }
+ return true;
+ }
+
+ private String groupsToString() {
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < mReadyGroups.size(); ++i) {
+ if (i != 0) b.append(',');
+ b.append(mReadyGroups.keyAt(i)).append(':')
+ .append(mReadyGroups.valueAt(i));
+ }
+ return b.toString();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index cc63c4922c32..929ec3b929b2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -16,24 +16,39 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.WindowManager;
-import android.window.IRemoteTransition;
+import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
+import android.window.RemoteTransition;
+import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.LocalServices;
+import com.android.server.statusbar.StatusBarManagerInternal;
import java.util.ArrayList;
+import java.util.function.LongConsumer;
/**
* Handles all the aspects of recording and synchronizing transitions.
@@ -41,8 +56,25 @@ import java.util.ArrayList;
class TransitionController {
private static final String TAG = "TransitionController";
+ /** The same as legacy APP_TRANSITION_TIMEOUT_MS. */
+ private static final int DEFAULT_TIMEOUT_MS = 5000;
+ /** Less duration for CHANGE type because it does not involve app startup. */
+ private static final int CHANGE_TIMEOUT_MS = 2000;
+
+ // State constants to line-up with legacy app-transition proto expectations.
+ private static final int LEGACY_STATE_IDLE = 0;
+ private static final int LEGACY_STATE_READY = 1;
+ private static final int LEGACY_STATE_RUNNING = 2;
+
private ITransitionPlayer mTransitionPlayer;
+ final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
+
+ private IApplicationThread mTransitionPlayerThread;
final ActivityTaskManagerService mAtm;
+ final TaskSnapshotController mTaskSnapshotController;
+
+ private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners =
+ new ArrayList<>();
/**
* Currently playing transitions (in the order they were started). When finished, records are
@@ -50,20 +82,32 @@ class TransitionController {
*/
private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>();
- private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> {
- // clean-up/finish any playing transitions.
- for (int i = 0; i < mPlayingTransitions.size(); ++i) {
- mPlayingTransitions.get(i).cleanUpOnFailure();
- }
- mPlayingTransitions.clear();
- mTransitionPlayer = null;
- };
+ final Lock mRunningLock = new Lock();
+
+ private final IBinder.DeathRecipient mTransitionPlayerDeath;
/** The transition currently being constructed (collecting participants). */
private Transition mCollectingTransition = null;
- TransitionController(ActivityTaskManagerService atm) {
+ // TODO(b/188595497): remove when not needed.
+ final StatusBarManagerInternal mStatusBar;
+
+ TransitionController(ActivityTaskManagerService atm,
+ TaskSnapshotController taskSnapshotController) {
mAtm = atm;
+ mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
+ mTaskSnapshotController = taskSnapshotController;
+ mTransitionPlayerDeath = () -> {
+ synchronized (mAtm.mGlobalLock) {
+ // Clean-up/finish any playing transitions.
+ for (int i = 0; i < mPlayingTransitions.size(); ++i) {
+ mPlayingTransitions.get(i).cleanUpOnFailure();
+ }
+ mPlayingTransitions.clear();
+ mTransitionPlayer = null;
+ mRunningLock.doNotifyLocked();
+ }
+ };
}
/** @see #createTransition(int, int) */
@@ -76,7 +120,7 @@ class TransitionController {
* Creates a transition. It can immediately collect participants.
*/
@NonNull
- Transition createTransition(@WindowManager.TransitionType int type,
+ private Transition createTransition(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags) {
if (mTransitionPlayer == null) {
throw new IllegalStateException("Shell Transitions not enabled");
@@ -84,20 +128,31 @@ class TransitionController {
if (mCollectingTransition != null) {
throw new IllegalStateException("Simultaneous transitions not supported yet.");
}
- mCollectingTransition = new Transition(type, flags, this, mAtm.mWindowManager.mSyncEngine);
+ // Distinguish change type because the response time is usually expected to be not too long.
+ final long timeoutMs = type == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
+ mCollectingTransition = new Transition(type, flags, timeoutMs, this,
+ mAtm.mWindowManager.mSyncEngine);
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s",
mCollectingTransition);
+ dispatchLegacyAppTransitionPending();
return mCollectingTransition;
}
- void registerTransitionPlayer(@Nullable ITransitionPlayer player) {
+ void registerTransitionPlayer(@Nullable ITransitionPlayer player,
+ @Nullable IApplicationThread appThread) {
try {
+ // Note: asBinder() can be null if player is same process (likely in a test).
if (mTransitionPlayer != null) {
- mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
+ if (mTransitionPlayer.asBinder() != null) {
+ mTransitionPlayer.asBinder().unlinkToDeath(mTransitionPlayerDeath, 0);
+ }
mTransitionPlayer = null;
}
- player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
+ if (player.asBinder() != null) {
+ player.asBinder().linkToDeath(mTransitionPlayerDeath, 0);
+ }
mTransitionPlayer = player;
+ mTransitionPlayerThread = appThread;
} catch (RemoteException e) {
throw new RuntimeException("Unable to set transition player");
}
@@ -155,21 +210,42 @@ class TransitionController {
}
/**
- * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition)
+ * @return {@code true} if {@param ar} is part of a transient-launch activity in an active
+ * transition.
+ */
+ boolean isTransientLaunch(@NonNull ActivityRecord ar) {
+ if (mCollectingTransition != null && mCollectingTransition.isTransientLaunch(ar)) {
+ return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
+ }
+ return false;
+ }
+
+ @WindowManager.TransitionType
+ int getCollectingTransitionType() {
+ return mCollectingTransition != null ? mCollectingTransition.mType : TRANSIT_NONE;
+ }
+
+ /**
+ * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
*/
@Nullable
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
- @Nullable WindowContainer trigger) {
- return requestTransitionIfNeeded(type, 0 /* flags */, trigger);
+ @NonNull WindowContainer trigger) {
+ return requestTransitionIfNeeded(type, 0 /* flags */, trigger, trigger /* readyGroupRef */);
}
/**
- * @see #requestTransitionIfNeeded(int, int, WindowContainer, IRemoteTransition)
+ * @see #requestTransitionIfNeeded(int, int, WindowContainer, WindowContainer, RemoteTransition)
*/
@Nullable
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
- @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
- return requestTransitionIfNeeded(type, flags, trigger, null /* remote */);
+ @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
+ @NonNull WindowContainer readyGroupRef) {
+ return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
+ null /* remoteTransition */);
}
private static boolean isExistenceType(@WindowManager.TransitionType int type) {
@@ -180,19 +256,24 @@ class TransitionController {
* If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to
* start it. Collection can start immediately.
* @param trigger if non-null, this is the first container that will be collected
+ * @param readyGroupRef Used to identify which ready-group this request is for.
* @return the created transition if created or null otherwise.
*/
@Nullable
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
@WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
- @Nullable IRemoteTransition remoteTransition) {
+ @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition) {
if (mTransitionPlayer == null) {
return null;
}
Transition newTransition = null;
if (isCollecting()) {
// Make the collecting transition wait until this request is ready.
- mCollectingTransition.setReady(false);
+ mCollectingTransition.setReady(readyGroupRef, false);
+ if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
+ // Add keyguard flag to dismiss keyguard
+ mCollectingTransition.addFlag(flags);
+ }
} else {
newTransition = requestStartTransition(createTransition(type, flags),
trigger != null ? trigger.asTask() : null, remoteTransition);
@@ -210,7 +291,7 @@ class TransitionController {
/** Asks the transition player (shell) to start a created but not yet started transition. */
@NonNull
Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
- @Nullable IRemoteTransition remoteTransition) {
+ @Nullable RemoteTransition remoteTransition) {
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
@@ -228,6 +309,22 @@ class TransitionController {
return transition;
}
+ /** Requests transition for a window container which will be removed or invisible. */
+ void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
+ if (mTransitionPlayer == null) return;
+ if (wc.isVisibleRequested()) {
+ if (!isCollecting()) {
+ requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
+ wc.asTask(), null /* remoteTransition */);
+ }
+ collectExistenceChange(wc);
+ } else {
+ // Removing a non-visible window doesn't require a transition, but if there is one
+ // collecting, this should be a member just in case.
+ collect(wc);
+ }
+ }
+
/** @see Transition#collect */
void collect(@NonNull WindowContainer wc) {
if (mCollectingTransition == null) return;
@@ -240,19 +337,28 @@ class TransitionController {
mCollectingTransition.collectExistenceChange(wc);
}
+ /** @see Transition#setOverrideAnimation */
+ void setOverrideAnimation(TransitionInfo.AnimationOptions options,
+ @Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.setOverrideAnimation(options, startCallback, finishCallback);
+ }
+
/** @see Transition#setReady */
- void setReady(boolean ready) {
+ void setReady(WindowContainer wc, boolean ready) {
if (mCollectingTransition == null) return;
- mCollectingTransition.setReady(ready);
+ mCollectingTransition.setReady(wc, ready);
}
/** @see Transition#setReady */
- void setReady() {
- setReady(true);
+ void setReady(WindowContainer wc) {
+ setReady(wc, true);
}
/** @see Transition#finishTransition */
void finishTransition(@NonNull IBinder token) {
+ // It is usually a no-op but make sure that the metric consumer is removed.
+ mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
final Transition record = Transition.fromBinder(token);
if (record == null || !mPlayingTransitions.contains(record)) {
Slog.e(TAG, "Trying to finish a non-playing transition " + token);
@@ -260,7 +366,11 @@ class TransitionController {
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
mPlayingTransitions.remove(record);
+ if (mPlayingTransitions.isEmpty()) {
+ setAnimationRunning(false /* running */);
+ }
record.finishTransition();
+ mRunningLock.doNotifyLocked();
}
void moveToPlaying(Transition transition) {
@@ -268,9 +378,22 @@ class TransitionController {
throw new IllegalStateException("Trying to move non-collecting transition to playing");
}
mCollectingTransition = null;
+ if (mPlayingTransitions.isEmpty()) {
+ setAnimationRunning(true /* running */);
+ }
mPlayingTransitions.add(transition);
}
+ private void setAnimationRunning(boolean running) {
+ if (mTransitionPlayerThread == null) return;
+ final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread);
+ if (wpc == null) {
+ Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread);
+ return;
+ }
+ wpc.setRunningRemoteAnimation(running);
+ }
+
void abort(Transition transition) {
if (transition != mCollectingTransition) {
throw new IllegalStateException("Too late to abort.");
@@ -279,4 +402,137 @@ class TransitionController {
mCollectingTransition = null;
}
+ /**
+ * Record that the launch of {@param activity} is transient (meaning its lifecycle is currently
+ * tied to the transition).
+ */
+ void setTransientLaunch(@NonNull ActivityRecord activity) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.setTransientLaunch(activity);
+
+ // TODO(b/188669821): Remove once legacy recents behavior is moved to shell.
+ // Also interpret HOME transient launch as recents
+ if (activity.getActivityType() == ACTIVITY_TYPE_HOME) {
+ mCollectingTransition.addFlag(TRANSIT_FLAG_IS_RECENTS);
+ }
+ }
+
+ void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
+ final Transition transition = Transition.fromBinder(token);
+ if (transition == null || !mPlayingTransitions.contains(transition)) {
+ Slog.e(TAG, "Transition isn't playing: " + token);
+ return;
+ }
+ transition.legacyRestoreNavigationBarFromApp();
+ }
+
+ void registerLegacyListener(WindowManagerInternal.AppTransitionListener listener) {
+ mLegacyListeners.add(listener);
+ }
+
+ void unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener) {
+ mLegacyListeners.remove(listener);
+ }
+
+ void dispatchLegacyAppTransitionPending() {
+ for (int i = 0; i < mLegacyListeners.size(); ++i) {
+ mLegacyListeners.get(i).onAppTransitionPendingLocked();
+ }
+ }
+
+ void dispatchLegacyAppTransitionStarting(TransitionInfo info) {
+ final boolean keyguardGoingAway = info.isKeyguardGoingAway();
+ for (int i = 0; i < mLegacyListeners.size(); ++i) {
+ // TODO(shell-transitions): handle (un)occlude transition.
+ mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
+ false /* keyguardOcclude */, 0 /* durationHint */,
+ SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+ }
+ }
+
+ void dispatchLegacyAppTransitionFinished(ActivityRecord ar) {
+ for (int i = 0; i < mLegacyListeners.size(); ++i) {
+ mLegacyListeners.get(i).onAppTransitionFinishedLocked(ar.token);
+ }
+ }
+
+ void dispatchLegacyAppTransitionCancelled() {
+ for (int i = 0; i < mLegacyListeners.size(); ++i) {
+ mLegacyListeners.get(i).onAppTransitionCancelledLocked(
+ false /* keyguardGoingAway */);
+ }
+ }
+
+ void dumpDebugLegacy(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ int state = LEGACY_STATE_IDLE;
+ if (!mPlayingTransitions.isEmpty()) {
+ state = LEGACY_STATE_RUNNING;
+ } else if (mCollectingTransition != null && mCollectingTransition.getLegacyIsReady()) {
+ state = LEGACY_STATE_READY;
+ }
+ proto.write(AppTransitionProto.APP_TRANSITION_STATE, state);
+ proto.end(token);
+ }
+
+ static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
+ private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
+
+ void associate(IBinder transitionToken, LongConsumer consumer) {
+ synchronized (mMetricConsumers) {
+ mMetricConsumers.put(transitionToken, consumer);
+ }
+ }
+
+ @Override
+ public void reportAnimationStart(IBinder transitionToken, long startTime) {
+ final LongConsumer c;
+ synchronized (mMetricConsumers) {
+ if (mMetricConsumers.isEmpty()) return;
+ c = mMetricConsumers.remove(transitionToken);
+ }
+ if (c != null) {
+ c.accept(startTime);
+ }
+ }
+ }
+
+ class Lock {
+ private int mTransitionWaiters = 0;
+ void runWhenIdle(long timeout, Runnable r) {
+ synchronized (mAtm.mGlobalLock) {
+ if (!inTransition()) {
+ r.run();
+ return;
+ }
+ mTransitionWaiters += 1;
+ }
+ final long startTime = SystemClock.uptimeMillis();
+ final long endTime = startTime + timeout;
+ while (true) {
+ synchronized (mAtm.mGlobalLock) {
+ if (!inTransition() || SystemClock.uptimeMillis() > endTime) {
+ mTransitionWaiters -= 1;
+ r.run();
+ return;
+ }
+ }
+ synchronized (this) {
+ try {
+ this.wait(timeout);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ }
+ }
+
+ void doNotifyLocked() {
+ synchronized (this) {
+ if (mTransitionWaiters > 0) {
+ this.notifyAll();
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
index 416b9dfe50b4..4a5a20e57804 100644
--- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -20,6 +20,7 @@ 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;
+import android.annotation.NonNull;
import android.graphics.Point;
import android.os.SystemClock;
import android.util.proto.ProtoOutputStream;
@@ -64,18 +65,17 @@ class WallpaperAnimationAdapter implements AnimationAdapter {
*
* @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
*/
- public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+ public static RemoteAnimationTarget[] startWallpaperAnimations(DisplayContent displayContent,
long durationHint, long statusBarTransitionDelay,
Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+ if (!shouldStartWallpaperAnimation(displayContent)) {
+ ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,
+ "\tWallpaper of display=%s is not visible", displayContent);
+ return new RemoteAnimationTarget[0];
+ }
final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
- service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
- if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tNot visible=%s", wallpaperWindow);
- return;
- }
-
- ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "\tvisible=%s", wallpaperWindow);
+ displayContent.forAllWallpaperWindows(wallpaperWindow -> {
final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
wallpaperWindow, durationHint, statusBarTransitionDelay,
animationCanceledRunnable);
@@ -87,13 +87,17 @@ class WallpaperAnimationAdapter implements AnimationAdapter {
return targets.toArray(new RemoteAnimationTarget[targets.size()]);
}
+ static boolean shouldStartWallpaperAnimation(DisplayContent displayContent) {
+ return displayContent.mWallpaperController.isWallpaperVisible();
+ }
+
/**
* Create a remote animation target for this animation adapter.
*/
RemoteAnimationTarget createRemoteAnimationTarget() {
mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null,
mWallpaperToken.getPrefixOrderIndex(), new Point(), null, null,
- mWallpaperToken.getWindowConfiguration(), true, null, null, null);
+ mWallpaperToken.getWindowConfiguration(), true, null, null, null, false);
return mTarget;
}
@@ -134,7 +138,8 @@ class WallpaperAnimationAdapter implements AnimationAdapter {
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
- @AnimationType int type, SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+ @AnimationType int type,
+ @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");
// Restore z-layering until client has a chance to modify it.
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 1f2a9a29932c..4b2aa0f76272 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -16,18 +16,20 @@
package com.android.server.wm;
+import static android.app.WallpaperManager.COMMAND_FREEZE;
+import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
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.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;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
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.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
@@ -47,6 +49,8 @@ import android.view.WindowManager;
import android.view.animation.Animation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLogImpl;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import java.io.PrintWriter;
@@ -79,6 +83,8 @@ class WallpaperController {
private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
private final float mMaxWallpaperScale;
+ // Whether COMMAND_FREEZE was dispatched.
+ private boolean mLastFrozen = false;
// This is set when we are waiting for a wallpaper to tell us it is done
// changing its scroll position.
@@ -124,7 +130,7 @@ class WallpaperController {
}
mFindResults.resetTopWallpaper = true;
- if (mService.mAtmService.getTransitionController().getTransitionPlayer() == null) {
+ if (!w.mTransitionController.isShellTransitionsEnabled()) {
if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
&& !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
// If this window's app token is hidden and not animating, it is of no interest.
@@ -195,6 +201,7 @@ class WallpaperController {
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Win " + w + ": token animating, looking behind.");
}
+ mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
// Found a target! End search.
return true;
}
@@ -287,10 +294,11 @@ class WallpaperController {
for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.setVisibility(false);
- if (DEBUG_WALLPAPER_LIGHT && token.isVisible()) {
- Slog.d(TAG, "Hiding wallpaper " + token
- + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, " "));
+ if (ProtoLogImpl.isEnabled(WM_DEBUG_WALLPAPER) && token.isVisible()) {
+ ProtoLog.d(WM_DEBUG_WALLPAPER,
+ "Hiding wallpaper %s from %s target=%s prev=%s callers=%s",
+ token, winGoingAway, mWallpaperTarget, mPrevWallpaperTarget,
+ Debug.getCallers(5));
}
}
}
@@ -425,20 +433,25 @@ class WallpaperController {
Bundle sendWindowWallpaperCommand(
WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) {
if (window == mWallpaperTarget || window == mPrevWallpaperTarget) {
- boolean doWait = sync;
- for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
- final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.sendWindowWallpaperCommand(action, x, y, z, extras, sync);
- }
-
- if (doWait) {
- // TODO: Need to wait for result.
- }
+ sendWindowWallpaperCommand(action, x, y, z, extras, sync);
}
return null;
}
+ private void sendWindowWallpaperCommand(
+ String action, int x, int y, int z, Bundle extras, boolean sync) {
+ boolean doWait = sync;
+ for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
+ final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
+ token.sendWindowWallpaperCommand(action, x, y, z, extras, sync);
+ }
+
+ if (doWait) {
+ // TODO: Need to wait for result.
+ }
+ }
+
private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
WindowState target = mWallpaperTarget;
if (target != null) {
@@ -535,15 +548,15 @@ class WallpaperController {
// Is it time to stop animating?
if (!mPrevWallpaperTarget.isAnimatingLw()) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "No longer animating wallpaper targets!");
mPrevWallpaperTarget = null;
mWallpaperTarget = wallpaperTarget;
}
return;
}
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New wallpaper target: %s prevTarget: %s caller=%s",
+ wallpaperTarget, mWallpaperTarget, Debug.getCallers(5));
mPrevWallpaperTarget = null;
@@ -561,8 +574,8 @@ class WallpaperController {
// then we are in our super special mode!
boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
boolean foundAnim = wallpaperTarget.isAnimatingLw();
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
- "New animation: " + foundAnim + " old animation: " + oldAnim);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "New animation: %s old animation: %s",
+ foundAnim, oldAnim);
if (!foundAnim || !oldAnim) {
return;
@@ -577,14 +590,14 @@ class WallpaperController {
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
&& !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
- + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
- + " hidden=" + newTargetHidden);
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ + "old: %s hidden=%b new: %s hidden=%b",
+ prevWallpaperTarget, oldTargetHidden, wallpaperTarget, newTargetHidden);
mPrevWallpaperTarget = prevWallpaperTarget;
if (newTargetHidden && !oldTargetHidden) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "Old wallpaper still the target.");
// Use the old target if new target is hidden but old target
// is not. If they're both hidden, still use the new target.
mWallpaperTarget = prevWallpaperTarget;
@@ -645,8 +658,15 @@ class WallpaperController {
updateWallpaperTokens(visible);
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
- + " prev=" + mPrevWallpaperTarget);
+ if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
+ mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
+ sendWindowWallpaperCommand(
+ mFindResults.isWallpaperTargetForLetterbox ? COMMAND_FREEZE : COMMAND_UNFREEZE,
+ /* x= */ 0, /* y= */ 0, /* z= */ 0, /* extras= */ null, /* sync= */ false);
+ }
+
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "New wallpaper: target=%s prev=%s",
+ mWallpaperTarget, mPrevWallpaperTarget);
}
boolean processWallpaperDrawPendingTimeout() {
@@ -782,6 +802,18 @@ class WallpaperController {
wallpaperBuffer.getHardwareBuffer(), wallpaperBuffer.getColorSpace());
}
+ /**
+ * Mirrors the visible wallpaper if it's available.
+ *
+ * @return A SurfaceControl for the parent of the mirrored wallpaper.
+ */
+ SurfaceControl mirrorWallpaperSurface() {
+ final WindowState wallpaperWindowState = getTopVisibleWallpaper();
+ return wallpaperWindowState != null
+ ? SurfaceControl.mirrorSurface(wallpaperWindowState.getSurfaceControl())
+ : null;
+ }
+
WindowState getTopVisibleWallpaper() {
mTmpTopWallpaper = null;
@@ -842,6 +874,7 @@ class WallpaperController {
boolean useTopWallpaperAsTarget = false;
WindowState wallpaperTarget = null;
boolean resetTopWallpaper = false;
+ boolean isWallpaperTargetForLetterbox = false;
void setTopWallpaper(WindowState win) {
topWallpaper = win;
@@ -855,11 +888,16 @@ class WallpaperController {
useTopWallpaperAsTarget = topWallpaperAsTarget;
}
+ void setIsWallpaperTargetForLetterbox(boolean isWallpaperTargetForLetterbox) {
+ this.isWallpaperTargetForLetterbox = isWallpaperTargetForLetterbox;
+ }
+
void reset() {
topWallpaper = null;
wallpaperTarget = null;
useTopWallpaperAsTarget = false;
resetTopWallpaper = false;
+ isWallpaperTargetForLetterbox = false;
}
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index b54e8b7a7b4e..3a639f50603f 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -20,7 +20,7 @@ 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.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -28,7 +28,6 @@ import android.annotation.Nullable;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.Slog;
import android.view.DisplayInfo;
import android.view.ViewGroup;
import android.view.WindowManager;
@@ -107,12 +106,12 @@ class WallpaperWindowToken extends WindowToken {
void updateWallpaperWindows(boolean visible) {
if (isVisible() != visible) {
- if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
- "Wallpaper token " + token + " visible=" + visible);
+ ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
+ token, visible);
setVisibility(visible);
}
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
- if (mWmService.mAtmService.getTransitionController().getTransitionPlayer() != null) {
+ if (mTransitionController.isShellTransitionsEnabled()) {
return;
}
@@ -157,12 +156,12 @@ class WallpaperWindowToken extends WindowToken {
*/
void setVisibility(boolean visible) {
// Before setting mVisibleRequested so we can track changes.
- mWmService.mAtmService.getTransitionController().collect(this);
+ mTransitionController.collect(this);
setVisibleRequested(visible);
// If in a transition, defer commits for activities that are going invisible
- if (!visible && (mWmService.mAtmService.getTransitionController().inTransition()
+ if (!visible && (mTransitionController.inTransition()
|| getDisplayContent().mAppTransition.isRunning())) {
return;
}
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index 66ab094f0994..9780d3317e11 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -31,6 +31,7 @@ import android.util.TypedValue;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
+import android.view.WindowManagerPolicyConstants;
/**
* Displays a watermark on top of the window manager's windows.
@@ -117,7 +118,7 @@ class Watermark {
.setFormat(PixelFormat.TRANSLUCENT)
.setCallsite(TITLE)
.build();
- t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 100)
+ t.setLayer(ctrl, WindowManagerPolicyConstants.WATERMARK_LAYER)
.setPosition(ctrl, 0, 0)
.show(ctrl);
// Ensure we aren't considered as obscuring for Input purposes.
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index eb32486d6023..4a43f4f73eda 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -164,7 +164,7 @@ public class WindowAnimator {
final DisplayContent dc = root.getDisplayContent(displayId);
dc.checkAppWindowsReadyToShow();
- if (accessibilityController != null) {
+ if (accessibilityController.hasCallbacks()) {
accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId,
mTransaction);
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d99aed1b409a..5af9147cd56f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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;
@@ -30,10 +31,7 @@ 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.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -41,6 +39,7 @@ 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.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.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
import static com.android.server.wm.IdentifierProto.TITLE;
@@ -55,6 +54,7 @@ import static com.android.server.wm.WindowContainerProto.CONFIGURATION_CONTAINER
import static com.android.server.wm.WindowContainerProto.IDENTIFIER;
import static com.android.server.wm.WindowContainerProto.ORIENTATION;
import static com.android.server.wm.WindowContainerProto.SURFACE_ANIMATOR;
+import static com.android.server.wm.WindowContainerProto.SURFACE_CONTROL;
import static com.android.server.wm.WindowContainerProto.VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -67,8 +67,6 @@ import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
-import android.app.WindowConfiguration;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -89,6 +87,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
import android.view.SurfaceSession;
+import android.view.TaskTransitionSpec;
import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
import android.view.animation.Animation;
@@ -109,6 +108,8 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -125,26 +126,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowContainer" : TAG_WM;
- /** Animation layer that happens above all animating {@link Task}s. */
- static final int ANIMATION_LAYER_STANDARD = 0;
-
- /** Animation layer that happens above all {@link Task}s. */
- static final int ANIMATION_LAYER_BOOSTED = 1;
-
- /**
- * Animation layer that is reserved for {@link WindowConfiguration#ACTIVITY_TYPE_HOME}
- * activities and all activities that are being controlled by the recents animation. This
- * layer is generally below all {@link Task}s.
- */
- static final int ANIMATION_LAYER_HOME = 2;
-
- @IntDef(prefix = { "ANIMATION_LAYER_" }, value = {
- ANIMATION_LAYER_STANDARD,
- ANIMATION_LAYER_BOOSTED,
- ANIMATION_LAYER_HOME,
- })
- @interface AnimationLayer {}
-
static final int POSITION_TOP = Integer.MAX_VALUE;
static final int POSITION_BOTTOM = Integer.MIN_VALUE;
@@ -195,10 +176,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* Applied as part of the animation pass in "prepareSurfaces".
*/
protected final SurfaceAnimator mSurfaceAnimator;
- private boolean mAnyParentAnimating;
+
+ /** The parent leash added for animation. */
+ @Nullable
+ private SurfaceControl mAnimationLeash;
final SurfaceFreezer mSurfaceFreezer;
protected final WindowManagerService mWmService;
+ final TransitionController mTransitionController;
/**
* Sources which triggered a surface animation on this container. An animation target can be
@@ -329,6 +314,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
WindowContainer(WindowManagerService wms) {
mWmService = wms;
+ mTransitionController = mWmService.mAtmService.getTransitionController();
mPendingTransaction = wms.mTransactionFactory.get();
mSyncTransaction = wms.mTransactionFactory.get();
mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
@@ -362,6 +348,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
throw new IllegalArgumentException("reparent: can't reparent to null " + this);
}
+ if (newParent == this) {
+ throw new IllegalArgumentException("Can not reparent to itself " + this);
+ }
+
final WindowContainer oldParent = mParent;
if (mParent == newParent) {
throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
@@ -624,7 +614,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final DisplayContent dc = getDisplayContent();
if (dc != null) {
mSurfaceFreezer.unfreeze(getSyncTransaction());
- dc.mChangingContainers.remove(this);
}
while (!mChildren.isEmpty()) {
final E child = mChildren.peekLast();
@@ -865,7 +854,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
boolean isAttached() {
- return getDisplayArea() != null;
+ WindowContainer parent = getParent();
+ return parent != null && parent.isAttached();
}
void setWaitingForDrawnIfResizingChanged() {
@@ -1000,6 +990,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return mDisplayContent != null && mDisplayContent.mChangingContainers.contains(this);
}
+ boolean inTransition() {
+ return mTransitionController.inTransition(this);
+ }
+
void sendAppVisibilityToClients() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
@@ -1072,9 +1066,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// part of a change transition.
if (!visible) {
mSurfaceFreezer.unfreeze(getSyncTransaction());
- if (mDisplayContent != null) {
- mDisplayContent.mChangingContainers.remove(this);
- }
}
WindowContainer parent = getParent();
if (parent != null) {
@@ -1389,6 +1380,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ /** Returns whether the window should be shown for current user. */
boolean showToCurrentUser() {
return true;
}
@@ -1685,6 +1677,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
+ boolean forAllLeafTaskFragments(Function<TaskFragment, Boolean> callback) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ if (mChildren.get(i).forAllLeafTaskFragments(callback)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* For all root tasks at or below this container call the callback.
*
@@ -1740,6 +1741,28 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ /**
+ * For all task fragments at or below this container call the callback.
+ *
+ * @param callback Callback to be called for every task.
+ */
+ void forAllTaskFragments(Consumer<TaskFragment> callback) {
+ forAllTaskFragments(callback, true /*traverseTopToBottom*/);
+ }
+
+ void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+ final int count = mChildren.size();
+ if (traverseTopToBottom) {
+ for (int i = count - 1; i >= 0; --i) {
+ mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom);
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllTaskFragments(callback, traverseTopToBottom);
+ }
+ }
+ }
+
void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
if (traverseTopToBottom) {
@@ -1753,6 +1776,19 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ void forAllLeafTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
+ final int count = mChildren.size();
+ if (traverseTopToBottom) {
+ for (int i = count - 1; i >= 0; --i) {
+ mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom);
+ }
+ } else {
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllLeafTaskFragments(callback, traverseTopToBottom);
+ }
+ }
+ }
+
/**
* For all root tasks at or below this container call the callback.
*
@@ -2254,7 +2290,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void assignLayer(Transaction t, int layer) {
// Don't assign layers while a transition animation is playing
// TODO(b/173528115): establish robust best-practices around z-order fighting.
- if (mWmService.mAtmService.getTransitionController().isPlaying()) return;
+ if (mTransitionController.isPlaying()) return;
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
setLayer(t, layer);
@@ -2278,10 +2314,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
protected void setLayer(Transaction t, int layer) {
-
- // Route through surface animator to accommodate that our surface control might be
- // attached to the leash, and leash is attached to parent container.
- mSurfaceAnimator.setLayer(t, layer);
+ if (mSurfaceFreezer.hasLeash()) {
+ // When the freezer has created animation leash parent for the window, set the layer
+ // there instead.
+ mSurfaceFreezer.setLayer(t, layer);
+ } else {
+ // Route through surface animator to accommodate that our surface control might be
+ // attached to the leash, and leash is attached to parent container.
+ mSurfaceAnimator.setLayer(t, layer);
+ }
}
int getLastLayer() {
@@ -2289,10 +2330,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
protected void setRelativeLayer(Transaction t, SurfaceControl relativeTo, int layer) {
-
- // Route through surface animator to accommodate that our surface control might be
- // attached to the leash, and leash is attached to parent container.
- mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+ if (mSurfaceFreezer.hasLeash()) {
+ // When the freezer has created animation leash parent for the window, set the layer
+ // there instead.
+ mSurfaceFreezer.setRelativeLayer(t, relativeTo, layer);
+ } else {
+ // Route through surface animator to accommodate that our surface control might be
+ // attached to the leash, and leash is attached to parent container.
+ mSurfaceAnimator.setRelativeLayer(t, relativeTo, layer);
+ }
}
protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
@@ -2362,6 +2408,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (mSurfaceAnimator.isAnimating()) {
mSurfaceAnimator.dumpDebug(proto, SURFACE_ANIMATOR);
}
+ if (mSurfaceControl != null) {
+ mSurfaceControl.dumpDebug(proto, SURFACE_CONTROL);
+ }
// add children to proto
for (int i = 0; i < getChildCount(); i++) {
@@ -2511,11 +2560,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* @param animationFinishedCallback The callback being triggered when the animation finishes.
* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
* cancel call to the underlying AnimationAdapter.
+ * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no
+ * snapshot.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
- @Nullable Runnable animationCancelledCallback) {
+ @Nullable Runnable animationCancelledCallback,
+ @Nullable AnimationAdapter snapshotAnim) {
if (DEBUG_ANIM) {
Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
}
@@ -2523,14 +2575,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
- animationCancelledCallback, mSurfaceFreezer);
+ animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback) {
startAnimation(t, anim, hidden, type, animationFinishedCallback,
- null /* adapterAnimationCancelledCallback */);
+ null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -2548,13 +2600,61 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mSurfaceFreezer.unfreeze(getPendingTransaction());
}
+ /** Whether we can start change transition with this window and current display status. */
+ boolean canStartChangeTransition() {
+ return !mWmService.mDisableTransitionAnimation && mDisplayContent != null
+ && getSurfaceControl() != null && !mDisplayContent.inTransition()
+ && isVisible() && isVisibleRequested() && okToAnimate();
+ }
+
+ /**
+ * Initializes a change transition. See {@link SurfaceFreezer} for more information.
+ *
+ * For now, this will only be called for the following cases:
+ * 1. {@link Task} is changing windowing mode between fullscreen and freeform.
+ * 2. {@link TaskFragment} is organized and is changing window bounds.
+ * 3. {@link ActivityRecord} is reparented into an organized {@link TaskFragment}. (The
+ * transition will happen on the {@link TaskFragment} for this case).
+ *
+ * This shouldn't be called on other {@link WindowContainer} unless there is a valid
+ * use case.
+ *
+ * @param startBounds The original bounds (on screen) of the surface we are snapshotting.
+ * @param freezeTarget The surface to take snapshot from. If {@code null}, we will take a
+ * snapshot from {@link #getFreezeSnapshotTarget()}.
+ */
+ void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
+ mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
+ mDisplayContent.mChangingContainers.add(this);
+ // Calculate the relative position in parent container.
+ final Rect parentBounds = getParent().getBounds();
+ mTmpPoint.set(startBounds.left - parentBounds.left, startBounds.top - parentBounds.top);
+ mSurfaceFreezer.freeze(getSyncTransaction(), startBounds, mTmpPoint, freezeTarget);
+ }
+
+ void initializeChangeTransition(Rect startBounds) {
+ initializeChangeTransition(startBounds, null /* freezeTarget */);
+ }
+
ArraySet<WindowContainer> getAnimationSources() {
return mSurfaceAnimationSources;
}
@Override
public SurfaceControl getFreezeSnapshotTarget() {
- return null;
+ // Only allow freezing if this window is in a TRANSIT_CHANGE
+ if (!mDisplayContent.mAppTransition.containsTransitRequest(TRANSIT_CHANGE)
+ || !mDisplayContent.mChangingContainers.contains(this)) {
+ return null;
+ }
+ return getSurfaceControl();
+ }
+
+ @Override
+ public void onUnfrozen() {
+ if (mDisplayContent != null) {
+ mDisplayContent.mChangingContainers.remove(this);
+ }
}
@Override
@@ -2567,17 +2667,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return getParentSurfaceControl();
}
- /**
- * @return The layer on which all app animations are happening.
- */
- SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
- final WindowContainer parent = getParent();
- if (parent != null) {
- return parent.getAppAnimationLayer(animationLayer);
- }
- return null;
- }
-
// TODO: Remove this and use #getBounds() instead once we set an app transition animation
// on TaskStack.
Rect getAnimationBounds(int appRootTaskClipMode) {
@@ -2653,6 +2742,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// Separate position and size for use in animators.
final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
mTmpRect.set(screenBounds);
+ if (this.asTask() != null && isTaskTransitOld(transit)) {
+ this.asTask().adjustAnimationBoundsForTransition(mTmpRect);
+ }
getAnimationPosition(mTmpPoint);
mTmpRect.offsetTo(0, 0);
@@ -2668,6 +2760,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final RemoteAnimationController.RemoteAnimationRecord adapters =
controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
+ if (!isChanging) {
+ adapters.setMode(enter
+ ? RemoteAnimationTarget.MODE_OPENING
+ : RemoteAnimationTarget.MODE_CLOSING);
+ }
resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
} else if (isChanging) {
final float durationScale = mWmService.getTransitionAnimationScaleLocked();
@@ -2739,40 +2836,37 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mSurfaceAnimationSources.addAll(sources);
}
- TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
- boolean isSettingBackgroundColor = taskDisplayArea != null
- && isTransitionWithBackgroundColor(transit);
-
- if (isSettingBackgroundColor) {
- Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
- @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background);
+ AnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();
- taskDisplayArea.setBackgroundColor(backgroundColor);
+ if (isTaskTransitOld(transit)) {
+ animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());
+ // TODO: Remove when we migrate to shell (b/202383002)
+ if (mWmService.mTaskTransitionSpec != null) {
+ animationRunnerBuilder.hideInsetSourceViewOverflows(
+ mWmService.mTaskTransitionSpec.animationBoundInsets);
+ }
}
- final Runnable cleanUpCallback = isSettingBackgroundColor
- ? taskDisplayArea::clearBackgroundColor : () -> {};
-
- startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION,
- (type, anim) -> cleanUpCallback.run(),
- cleanUpCallback);
+ animationRunnerBuilder.build()
+ .startAnimation(getPendingTransaction(), adapter, !isVisible(),
+ ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
- if (thumbnailAdapter != null) {
- mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
- thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { });
- }
}
}
- private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) {
- return transit == TRANSIT_OLD_TASK_OPEN
- || transit == TRANSIT_OLD_TASK_CLOSE
- || transit == TRANSIT_OLD_TASK_TO_FRONT
- || transit == TRANSIT_OLD_TASK_TO_BACK;
+ private @ColorInt int getTaskAnimationBackgroundColor() {
+ Context uiContext = mDisplayContent.getDisplayPolicy().getSystemUiContext();
+ TaskTransitionSpec customSpec = mWmService.mTaskTransitionSpec;
+ @ColorInt int defaultFallbackColor = uiContext.getColor(R.color.overview_background);
+
+ if (customSpec != null && customSpec.backgroundColor != 0) {
+ return customSpec.backgroundColor;
+ }
+
+ return defaultFallbackColor;
}
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
@@ -2783,7 +2877,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
boolean isVoiceInteraction) {
if (isOrganized()
// TODO(b/161711458): Clean-up when moved to shell.
- && getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ && getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ && getWindowingMode() != WINDOWING_MODE_FREEFORM) {
return null;
}
@@ -2846,18 +2941,21 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
+ /**
+ * {@code true} to indicate that this container can be a candidate of
+ * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) animation
+ * target}. */
+ boolean canBeAnimationTarget() {
+ return false;
+ }
+
boolean okToDisplay() {
final DisplayContent dc = getDisplayContent();
return dc != null && dc.okToDisplay();
}
boolean okToAnimate() {
- return okToAnimate(false /* ignoreFrozen */);
- }
-
- boolean okToAnimate(boolean ignoreFrozen) {
- final DisplayContent dc = getDisplayContent();
- return dc != null && dc.okToAnimate(ignoreFrozen);
+ return okToAnimate(false /* ignoreFrozen */, false /* ignoreScreenOn */);
}
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
@@ -2885,6 +2983,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Override
public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
mLastLayer = -1;
+ mAnimationLeash = leash;
reassignLayer(t);
// Leash is now responsible for position, so set our position to 0.
@@ -2894,11 +2993,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Override
public void onAnimationLeashLost(Transaction t) {
mLastLayer = -1;
- mSurfaceFreezer.unfreeze(t);
+ mAnimationLeash = null;
reassignLayer(t);
updateSurfacePosition(t);
}
+ @Override
+ public SurfaceControl getAnimationLeash() {
+ return mAnimationLeash;
+ }
+
private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {
for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {
mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);
@@ -3049,7 +3153,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
void updateSurfacePosition(Transaction t) {
- if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
+ if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
return;
}
@@ -3124,6 +3228,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/** Cheap way of doing cast and instanceof. */
+ TaskFragment asTaskFragment() {
+ return null;
+ }
+
+ /** Cheap way of doing cast and instanceof. */
WindowToken asWindowToken() {
return null;
}
@@ -3166,6 +3275,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
+ /** @return {@code true} if this is a container for embedded activities or tasks. */
+ boolean isEmbedded() {
+ return false;
+ }
+
/**
* @return {@code true} if this container's surface should be shown when it is created.
*/
@@ -3331,6 +3445,20 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
+ * Special helper to check that all windows are synced (vs just top one). This is only
+ * used to differentiate between starting-window vs full-drawn in activity-metrics reporting.
+ */
+ boolean allSyncFinished() {
+ if (!isVisibleRequested()) return true;
+ if (mSyncState != SYNC_STATE_READY) return false;
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final WindowContainer child = mChildren.get(i);
+ if (!child.allSyncFinished()) return false;
+ }
+ return true;
+ }
+
+ /**
* Called during reparent to handle sync state when the hierarchy changes.
* If this is in a sync group and gets reparented out, it will cancel syncing.
* If this is not in a sync group and gets parented into one, it will prepare itself.
@@ -3385,6 +3513,29 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
+ * Forces the receiver container to always use the configuration of the supplier container as
+ * its requested override configuration. It allows to propagate configuration without changing
+ * the relationship between child and parent.
+ */
+ static void overrideConfigurationPropagation(WindowContainer<?> receiver,
+ WindowContainer<?> supplier) {
+ final ConfigurationContainerListener listener = new ConfigurationContainerListener() {
+ @Override
+ public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
+ receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration());
+ }
+ };
+ supplier.registerConfigurationChangeListener(listener);
+ receiver.registerWindowContainerListener(new WindowContainerListener() {
+ @Override
+ public void onRemoved() {
+ receiver.unregisterWindowContainerListener(this);
+ supplier.unregisterConfigurationChangeListener(listener);
+ }
+ });
+ }
+
+ /**
* Returns the {@link WindowManager.LayoutParams.WindowType}.
*/
@WindowManager.LayoutParams.WindowType int getWindowType() {
@@ -3398,4 +3549,73 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getPendingTransaction().setSecure(mSurfaceControl, !canScreenshot);
return true;
}
+
+ private class AnimationRunnerBuilder {
+ /**
+ * Runs when the surface stops animating
+ */
+ private final List<Runnable> mOnAnimationFinished = new LinkedList<>();
+ /**
+ * Runs when the animation is cancelled but the surface is still animating
+ */
+ private final List<Runnable> mOnAnimationCancelled = new LinkedList<>();
+
+ private void setTaskBackgroundColor(@ColorInt int backgroundColor) {
+ TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
+
+ if (taskDisplayArea != null) {
+ taskDisplayArea.setBackgroundColor(backgroundColor);
+
+ // Atomic counter to make sure the clearColor callback is only called one.
+ // It will be called twice in the case we cancel the animation without restart
+ // (in that case it will run as the cancel and finished callbacks).
+ final AtomicInteger callbackCounter = new AtomicInteger(0);
+ final Runnable clearBackgroundColorHandler = () -> {
+ if (callbackCounter.getAndIncrement() == 0) {
+ taskDisplayArea.clearBackgroundColor();
+ }
+ };
+
+ // We want to make sure this is called both when the surface stops animating and
+ // also when an animation is cancelled (i.e. animation is replaced by another
+ // animation but and so the surface is still animating)
+ mOnAnimationFinished.add(clearBackgroundColorHandler);
+ mOnAnimationCancelled.add(clearBackgroundColorHandler);
+ }
+ }
+
+ private void hideInsetSourceViewOverflows(Set<Integer> insetTypes) {
+ final ArrayList<SurfaceControl> surfaceControls =
+ new ArrayList<>(insetTypes.size());
+
+ for (int insetType : insetTypes) {
+ InsetsSourceProvider insetProvider = getDisplayContent().getInsetsStateController()
+ .getSourceProvider(insetType);
+
+ // Will apply it immediately to current leash and to all future inset animations
+ // until we disable it.
+ insetProvider.setCropToProvidingInsetsBounds(getPendingTransaction());
+
+ // Only clear the size restriction of the inset once the surface animation is over
+ // and not if it's canceled to be replace by another animation.
+ mOnAnimationFinished.add(() -> {
+ insetProvider.removeCropToProvidingInsetsBounds(getPendingTransaction());
+ });
+ }
+ }
+
+ private IAnimationStarter build() {
+ return (Transaction t, AnimationAdapter adapter, boolean hidden,
+ @AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {
+ startAnimation(getPendingTransaction(), adapter, !isVisible(), type,
+ (animType, anim) -> mOnAnimationFinished.forEach(Runnable::run),
+ () -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim);
+ };
+ }
+ }
+
+ private interface IAnimationStarter {
+ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+ @AnimationType int type, @Nullable AnimationAdapter snapshotAnim);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 86e356a876b5..7956a112539e 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.view.Display.INVALID_DISPLAY;
+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;
@@ -80,7 +82,7 @@ class WindowContextListenerController {
* @param options a bundle used to pass window-related options.
*/
void registerWindowContainerListener(@NonNull IBinder clientToken,
- @NonNull WindowContainer container, int ownerUid, @WindowType int type,
+ @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type,
@Nullable Bundle options) {
WindowContextListenerImpl listener = mListeners.get(clientToken);
if (listener == null) {
@@ -103,6 +105,16 @@ class WindowContextListenerController {
listener.unregister();
}
+ void dispatchPendingConfigurationIfNeeded(int displayId) {
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ final WindowContextListenerImpl listener = mListeners.valueAt(i);
+ if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId
+ && listener.mHasPendingConfiguration) {
+ listener.reportConfigToWindowTokenClient();
+ }
+ }
+ }
+
/**
* Verifies if the caller is allowed to do the operation to the listener specified by
* {@code clientToken}.
@@ -138,7 +150,7 @@ class WindowContextListenerController {
return listener != null ? listener.mOptions : null;
}
- @Nullable WindowContainer getContainer(IBinder clientToken) {
+ @Nullable WindowContainer<?> getContainer(IBinder clientToken) {
final WindowContextListenerImpl listener = mListeners.get(clientToken);
return listener != null ? listener.mContainer : null;
}
@@ -161,9 +173,9 @@ class WindowContextListenerController {
@VisibleForTesting
class WindowContextListenerImpl implements WindowContainerListener {
- @NonNull private final IBinder mClientToken;
+ @NonNull private final IWindowToken mClientToken;
private final int mOwnerUid;
- @NonNull private WindowContainer mContainer;
+ @NonNull private WindowContainer<?> mContainer;
/**
* The options from {@link Context#createWindowContext(int, Bundle)}.
* <p>It can be used for choosing the {@link DisplayArea} where the window context
@@ -177,9 +189,11 @@ class WindowContextListenerController {
private int mLastReportedDisplay = INVALID_DISPLAY;
private Configuration mLastReportedConfig;
- private WindowContextListenerImpl(IBinder clientToken, WindowContainer container,
+ private boolean mHasPendingConfiguration;
+
+ private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container,
int ownerUid, @WindowType int type, @Nullable Bundle options) {
- mClientToken = clientToken;
+ mClientToken = IWindowToken.Stub.asInterface(clientToken);
mContainer = Objects.requireNonNull(container);
mOwnerUid = ownerUid;
mType = type;
@@ -191,17 +205,17 @@ class WindowContextListenerController {
mDeathRecipient = deathRecipient;
} catch (RemoteException e) {
ProtoLog.e(WM_ERROR, "Could not register window container listener token=%s, "
- + "container=%s", mClientToken, mContainer);
+ + "container=%s", clientToken, mContainer);
}
}
/** TEST ONLY: returns the {@link WindowContainer} of the listener */
@VisibleForTesting
- WindowContainer getWindowContainer() {
+ WindowContainer<?> getWindowContainer() {
return mContainer;
}
- private void updateContainer(@NonNull WindowContainer newContainer) {
+ private void updateContainer(@NonNull WindowContainer<?> newContainer) {
Objects.requireNonNull(newContainer);
if (mContainer.equals(newContainer)) {
@@ -214,17 +228,17 @@ class WindowContextListenerController {
}
private void register() {
+ final IBinder token = mClientToken.asBinder();
if (mDeathRecipient == null) {
- throw new IllegalStateException("Invalid client token: " + mClientToken);
+ throw new IllegalStateException("Invalid client token: " + token);
}
- mListeners.putIfAbsent(mClientToken, this);
+ mListeners.putIfAbsent(token, this);
mContainer.registerWindowContainerListener(this);
- reportConfigToWindowTokenClient();
}
private void unregister() {
mContainer.unregisterWindowContainerListener(this);
- mListeners.remove(mClientToken);
+ mListeners.remove(mClientToken.asBinder());
}
private void clear() {
@@ -244,14 +258,27 @@ class WindowContextListenerController {
private void reportConfigToWindowTokenClient() {
if (mDeathRecipient == null) {
- throw new IllegalStateException("Invalid client token: " + mClientToken);
+ throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder());
}
-
+ final DisplayContent dc = mContainer.getDisplayContent();
+ if (!dc.isReady()) {
+ // Do not report configuration when booting. The latest configuration will be sent
+ // when WindowManagerService#displayReady().
+ return;
+ }
+ // If the display of window context associated window container is suspended, don't
+ // report the configuration update. Note that we still dispatch the configuration update
+ // to WindowProviderService to make it compatible with Service#onConfigurationChanged.
+ // Service always receives #onConfigurationChanged callback regardless of display state.
+ if (!isWindowProviderService(mOptions) && isSuspendedState(dc.getDisplayInfo().state)) {
+ mHasPendingConfiguration = true;
+ return;
+ }
+ final Configuration config = mContainer.getConfiguration();
+ final int displayId = dc.getDisplayId();
if (mLastReportedConfig == null) {
mLastReportedConfig = new Configuration();
}
- final Configuration config = mContainer.getConfiguration();
- final int displayId = mContainer.getDisplayContent().getDisplayId();
if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) {
// No changes since last reported time.
return;
@@ -260,18 +287,18 @@ class WindowContextListenerController {
mLastReportedConfig.setTo(config);
mLastReportedDisplay = displayId;
- IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken);
try {
- windowTokenClient.onConfigurationChanged(config, displayId);
+ mClientToken.onConfigurationChanged(config, displayId);
} catch (RemoteException e) {
ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client.");
}
+ mHasPendingConfiguration = false;
}
@Override
public void onRemoved() {
if (mDeathRecipient == null) {
- throw new IllegalStateException("Invalid client token: " + mClientToken);
+ throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder());
}
final WindowToken windowToken = mContainer.asWindowToken();
if (windowToken != null && windowToken.isFromClient()) {
@@ -283,15 +310,14 @@ class WindowContextListenerController {
// If we cannot obtain the DisplayContent, the DisplayContent may also be removed.
// We should proceed the removal process.
if (dc != null) {
- final DisplayArea da = dc.findAreaForToken(windowToken);
+ final DisplayArea<?> da = dc.findAreaForToken(windowToken);
updateContainer(da);
return;
}
}
mDeathRecipient.unlinkToDeath();
- IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken);
try {
- windowTokenClient.onWindowTokenRemoved();
+ mClientToken.onWindowTokenRemoved();
} catch (RemoteException e) {
ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client.");
}
@@ -300,7 +326,7 @@ class WindowContextListenerController {
@Override
public String toString() {
- return "WindowContextListenerImpl{clientToken=" + mClientToken + ", "
+ return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", "
+ "container=" + mContainer + "}";
}
@@ -314,11 +340,11 @@ class WindowContextListenerController {
}
void linkToDeath() throws RemoteException {
- mClientToken.linkToDeath(this, 0);
+ mClientToken.asBinder().linkToDeath(this, 0);
}
void unlinkToDeath() {
- mClientToken.unlinkToDeath(this, 0);
+ mClientToken.asBinder().unlinkToDeath(this, 0);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index ffd6d21c1026..baea85439582 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -89,6 +89,11 @@ public class WindowFrames {
final Rect mCompatFrame = new Rect();
/**
+ * {@code true} if the window frame is a simulated frame and attached to a decor window.
+ */
+ boolean mIsSimulatingDecorWindow = false;
+
+ /**
* Whether the parent frame would have been different if there was no display cutout.
*/
private boolean mParentFrameWasClippedByDisplayCutout;
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 08404411c02b..c95470071e2d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -43,7 +43,6 @@ public class WindowManagerDebugConfig {
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_STARTING_WINDOW_VERBOSE = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
static final boolean DEBUG_DRAG = true;
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 4fac05c349c5..4e51a17e03e0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -40,6 +40,7 @@ import com.android.server.input.InputManagerService;
import com.android.server.policy.WindowManagerPolicy;
import java.util.List;
+import java.util.Set;
/**
* Window manager local system service interface.
@@ -54,17 +55,18 @@ public abstract class WindowManagerInternal {
*/
public interface AccessibilityControllerInternal {
/**
- * Enable the accessibility trace logging.
+ * Start tracing for the given logging types.
+ * @param loggingTypeFlags flags of the logging types enabled.
*/
- void startTrace();
+ void startTrace(long loggingTypeFlags);
/**
- * Disable the accessibility trace logging.
+ * Disable accessibility tracing for all logging types.
*/
void stopTrace();
/**
- * Is trace enabled or not.
+ * Is tracing enabled for any logging type.
*/
boolean isAccessibilityTracingEnabled();
@@ -73,20 +75,23 @@ public abstract class WindowManagerInternal {
*
* @param where A string to identify this log entry, which can be used to filter/search
* through the tracing file.
+ * @param loggingTypeFlags The flags for the logging types this log entry belongs to.
* @param callingParams The parameters for the method to be logged.
* @param a11yDump The proto byte array for a11y state when the entry is generated.
* @param callingUid The calling uid.
* @param stackTrace The stack trace, null if not needed.
+ * @param ignoreStackEntries The stack entries can be removed
*/
void logTrace(
- String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] stackTrace);
+ String where, long loggingTypeFlags, String callingParams, byte[] a11yDump,
+ int callingUid, StackTraceElement[] stackTrace, Set<String> ignoreStackEntries);
/**
* Add an accessibility trace entry.
*
* @param where A string to identify this log entry, which can be used to filter/search
* through the tracing file.
+ * @param loggingTypeFlags The flags for the logging types this log entry belongs to.
* @param callingParams The parameters for the method to be logged.
* @param a11yDump The proto byte array for a11y state when the entry is generated.
* @param callingUid The calling uid.
@@ -94,9 +99,11 @@ public abstract class WindowManagerInternal {
* @param timeStamp The time when the method to be logged is called.
* @param processId The calling process Id.
* @param threadId The calling thread Id.
+ * @param ignoreStackEntries The stack entries can be removed
*/
- void logTrace(String where, String callingParams, byte[] a11yDump, int callingUid,
- StackTraceElement[] callStack, long timeStamp, int processId, long threadId);
+ void logTrace(String where, long loggingTypeFlags, String callingParams,
+ byte[] a11yDump, int callingUid, StackTraceElement[] callStack, long timeStamp,
+ int processId, long threadId, Set<String> ignoreStackEntries);
}
/**
@@ -115,6 +122,16 @@ public abstract class WindowManagerInternal {
*/
void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows);
+
+ /**
+ * Called when the display is reparented and becomes an embedded
+ * display. The {@link WindowsForAccessibilityCallback} with the given embedded
+ * display will be replaced by the {@link WindowsForAccessibilityCallback}
+ * associated with its parent display at the same time.
+ *
+ * @param embeddedDisplayId The embedded display Id.
+ */
+ void onDisplayReparented(int embeddedDisplayId);
}
/**
@@ -143,11 +160,11 @@ public abstract class WindowManagerInternal {
void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
/**
- * Notifies that the rotation changed.
+ * Notifies that the display size is changed when rotation or the
+ * logical display is changed.
*
- * @param rotation The current rotation.
*/
- void onRotationChanged(int rotation);
+ void onDisplaySizeChanged();
/**
* Notifies that the context of the user changed. For example, an application
@@ -177,7 +194,7 @@ public abstract class WindowManagerInternal {
/**
* Called when a pending app transition gets cancelled.
*
- * @param keyguardGoingAway true if keyguard going away transition transition got cancelled.
+ * @param keyguardGoingAway true if keyguard going away transition got cancelled.
*/
public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {}
@@ -190,6 +207,7 @@ public abstract class WindowManagerInternal {
* Called when an app transition gets started
*
* @param keyguardGoingAway true if keyguard going away transition is started.
+ * @param keyguardOccluding true if keyguard (un)occlude transition is started.
* @param duration the total duration of the transition
* @param statusBarAnimationStartTime the desired start time for all visual animations in
* the status bar caused by this app transition in uptime millis
@@ -201,8 +219,9 @@ public abstract class WindowManagerInternal {
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
+ boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
return 0;
}
@@ -672,24 +691,43 @@ public abstract class WindowManagerInternal {
public abstract String getWindowName(@NonNull IBinder binder);
/**
- * Return the window name of IME Insets control target.
+ * The callback after the request of show/hide input method is sent.
*
+ * @param show Whether to show or hide input method.
+ * @param focusedToken The token of focused window.
+ * @param requestToken The token of window who requests the change.
* @param displayId The ID of the display which input method is currently focused.
- * @return The corresponding {@link WindowState#getName()}
+ * @return The information of the input method target.
*/
- public abstract @Nullable String getImeControlTargetNameForLogging(int displayId);
+ public abstract ImeTargetInfo onToggleImeRequested(boolean show,
+ @NonNull IBinder focusedToken, @NonNull IBinder requestToken, int displayId);
- /**
- * Return the current window name of the input method is on top of.
- *
- * Note that the concept of this window is only reparent the target window behind the input
- * method window, it may different with the window which reported by
- * {@code InputMethodManagerService#reportStartInput} which has input connection.
- *
- * @param displayId The ID of the display which input method is currently focused.
- * @return The corresponding {@link WindowState#getName()}
- */
- public abstract @Nullable String getImeTargetNameForLogging(int displayId);
+ /** The information of input method target when IME is requested to show or hide. */
+ public static class ImeTargetInfo {
+ public final String focusedWindowName;
+ public final String requestWindowName;
+
+ /** The window name of IME Insets control target. */
+ public final String imeControlTargetName;
+
+ /**
+ * The current window name of the input method is on top of.
+ * <p>
+ * Note that the concept of this window is only used to reparent the target window behind
+ * the input method window, it may be different from the window reported by
+ * {@link com.android.server.inputmethod.InputMethodManagerService#reportStartInput} which
+ * has input connection.
+ */
+ public final String imeLayerTargetName;
+
+ public ImeTargetInfo(String focusedWindowName, String requestWindowName,
+ String imeControlTargetName, String imeLayerTargetName) {
+ this.focusedWindowName = focusedWindowName;
+ this.requestWindowName = requestWindowName;
+ this.imeControlTargetName = imeControlTargetName;
+ this.imeLayerTargetName = imeLayerTargetName;
+ }
+ }
/**
* Moves the {@link WindowToken} {@code binder} to the display specified by {@code displayId}.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 45e833e84789..a38b31d07d05 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
import static android.Manifest.permission.INPUT_CONSUMER;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.MANAGE_APP_TOKENS;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
@@ -47,6 +48,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_P
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -56,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
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.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
@@ -84,8 +87,10 @@ import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
+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_BOOT;
@@ -167,8 +172,10 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
-import android.hardware.configstore.V1_0.ISurfaceFlingerConfigs;
import android.hardware.configstore.V1_0.OptionalBool;
+import android.hardware.configstore.V1_1.DisplayOrientation;
+import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs;
+import android.hardware.configstore.V1_1.OptionalDisplayOrientation;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
@@ -221,6 +228,7 @@ import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import android.view.Display;
+import android.view.DisplayAddress;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IAppTransitionAnimationSpecsFuture;
@@ -249,6 +257,7 @@ import android.view.InputEvent;
import android.view.InputWindowHandle;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
@@ -258,6 +267,7 @@ import android.view.ScrollCaptureResponse;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
+import android.view.TaskTransitionSpec;
import android.view.View;
import android.view.WindowContentFrameStats;
import android.view.WindowInsets;
@@ -339,26 +349,6 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean PROFILE_ORIENTATION = false;
static WindowState mFocusingWindow;
String mFocusingActivity;
- /** How much to multiply the policy's type layer, to reserve room
- * for multiple windows of the same type and Z-ordering adjustment
- * with TYPE_LAYER_OFFSET. */
- static final int TYPE_LAYER_MULTIPLIER = 10000;
-
- /** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
- * or below others in the same layer. */
- static final int TYPE_LAYER_OFFSET = 1000;
-
- /** How much to increment the layer for each window, to reserve room
- * for effect surfaces between them.
- */
- static final int WINDOW_LAYER_MULTIPLIER = 5;
-
- /**
- * Animation thumbnail is as far as possible below the window above
- * the thumbnail (or in other words as far as possible above the window
- * below it).
- */
- static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1;
/** The maximum length we will accept for a loaded animation duration:
* this is 10 seconds.
@@ -447,19 +437,19 @@ public class WindowManagerService extends IWindowManager.Stub
"persist.wm.enable_remote_keyguard_animation";
private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 0);
+ SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
*/
- public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = !sEnableShellTransitions
- && sEnableRemoteKeyguardAnimation >= 1;
+ public static final boolean sEnableRemoteKeyguardGoingAwayAnimation =
+ sEnableRemoteKeyguardAnimation >= 1;
/**
* @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
*/
- public static final boolean sEnableRemoteKeyguardOccludeAnimation = !sEnableShellTransitions
- && sEnableRemoteKeyguardAnimation >= 2;
+ public static final boolean sEnableRemoteKeyguardOccludeAnimation =
+ sEnableRemoteKeyguardAnimation >= 2;
/**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -467,6 +457,8 @@ public class WindowManagerService extends IWindowManager.Stub
*/
static final boolean ENABLE_FIXED_ROTATION_TRANSFORM =
SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true);
+ private @Surface.Rotation int mPrimaryDisplayOrientation = Surface.ROTATION_0;
+ private DisplayAddress mPrimaryDisplayPhysicalAddress;
// Enums for animation scale update types.
@Retention(RetentionPolicy.SOURCE)
@@ -650,7 +642,7 @@ public class WindowManagerService extends IWindowManager.Stub
/** List of window currently causing non-system overlay windows to be hidden. */
private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
- AccessibilityController mAccessibilityController;
+ final AccessibilityController mAccessibilityController;
private RecentsAnimationController mRecentsAnimationController;
Watermark mWatermark;
@@ -769,6 +761,11 @@ public class WindowManagerService extends IWindowManager.Stub
*/
final Handler mAnimationHandler = new Handler(AnimationThread.getHandler().getLooper());
+ /**
+ * Used during task transitions to allow SysUI and launcher to customize task transitions.
+ */
+ TaskTransitionSpec mTaskTransitionSpec;
+
boolean mHardKeyboardAvailable;
WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
SettingsObserver mSettingsObserver;
@@ -1238,7 +1235,9 @@ public class WindowManagerService extends IWindowManager.Stub
mAssistantOnTopOfDream = context.getResources().getBoolean(
com.android.internal.R.bool.config_assistantOnTopOfDream);
- mLetterboxConfiguration = new LetterboxConfiguration(context);
+ mLetterboxConfiguration = new LetterboxConfiguration(
+ // Using SysUI context to have access to Material colors extracted from Wallpaper.
+ ActivityThread.currentActivityThread().getSystemUiContext());
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1394,6 +1393,7 @@ public class WindowManagerService extends IWindowManager.Stub
mStartingSurfaceController = new StartingSurfaceController(this);
mBlurController = new BlurController(mContext, mPowerManager);
+ mAccessibilityController = new AccessibilityController(this);
}
DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
@@ -1459,7 +1459,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
- int displayId, int requestUserId, InsetsState requestedVisibility,
+ int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
Arrays.fill(outActiveControls, null);
@@ -1681,7 +1681,8 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
- win.updateRequestedVisibility(requestedVisibility);
+ win.setRequestedVisibilities(requestedVisibilities);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
@@ -1732,16 +1733,22 @@ public class WindowManagerService extends IWindowManager.Stub
&& mWindowContextListenerController.hasListener(windowContextToken)) {
final int windowContextType = mWindowContextListenerController
.getWindowType(windowContextToken);
+ final Bundle options = mWindowContextListenerController
+ .getOptions(windowContextToken);
if (type != windowContextType) {
ProtoLog.w(WM_ERROR, "Window types in WindowContext and"
+ " LayoutParams.type should match! Type from LayoutParams is %d,"
+ " but type from WindowContext is %d", type, windowContextType);
- return WindowManagerGlobal.ADD_INVALID_TYPE;
+ // We allow WindowProviderService to add window other than windowContextType,
+ // but the WindowProviderService won't be associated with the window's
+ // WindowToken.
+ if (!isWindowProviderService(options)) {
+ return WindowManagerGlobal.ADD_INVALID_TYPE;
+ }
+ } else {
+ mWindowContextListenerController.registerWindowContainerListener(
+ windowContextToken, token, callingUid, type, options);
}
- final Bundle options = mWindowContextListenerController
- .getOptions(windowContextToken);
- mWindowContextListenerController.registerWindowContainerListener(
- windowContextToken, token, callingUid, type, options);
}
// From now on, no exceptions or errors allowed!
@@ -1771,18 +1778,16 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
- final ActivityRecord tokenActivity = token.asActivityRecord();
- if (type == TYPE_APPLICATION_STARTING && tokenActivity != null) {
- tokenActivity.mStartingWindow = win;
- ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
- activity, win);
- }
-
boolean imMayMove = true;
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);
- if (type == TYPE_INPUT_METHOD) {
+ displayPolicy.setDropInputModePolicy(win, win.mAttrs);
+ if (type == TYPE_APPLICATION_STARTING && activity != null) {
+ activity.attachStartingWindow(win);
+ ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s",
+ activity, win);
+ } else if (type == TYPE_INPUT_METHOD) {
displayContent.setInputMethodWindowLocked(win);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
@@ -1808,7 +1813,8 @@ public class WindowManagerService extends IWindowManager.Stub
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true;
// Check if we need to prepare a transition for replacing window first.
- if (activity != null && activity.isVisible()
+ if (!win.mTransitionController.isShellTransitionsEnabled()
+ && activity != null && activity.isVisible()
&& !prepareWindowReplacementTransition(activity)) {
// If not, check if need to set up a dummy transition during display freeze
// so that the unfreeze wait for the apps to draw. This might be needed if
@@ -1856,7 +1862,7 @@ public class WindowManagerService extends IWindowManager.Stub
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
- if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
+ if (win.isVisibleRequestedOrAdding() && displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
}
@@ -2167,7 +2173,7 @@ public class WindowManagerService extends IWindowManager.Stub
mWindowPlacerLocked.performSurfacePlacement();
// We need to report touchable region changes to accessibility.
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.onSomeWindowResizedOrMovedWithCallingUid(
uid, w.getDisplayContent().getDisplayId());
}
@@ -2180,7 +2186,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
synchronized (mGlobalLock) {
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
WindowState window = mWindowMap.get(token);
if (window != null) {
mAccessibilityController.onRectangleOnScreenRequested(
@@ -2243,6 +2249,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (attrs != null) {
displayPolicy.adjustWindowParamsLw(win, attrs);
win.mToken.adjustWindowParams(win, attrs);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
int disableFlags =
(attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility) & DISABLE_MASK;
if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) {
@@ -2270,7 +2277,7 @@ public class WindowManagerService extends IWindowManager.Stub
win.mActivityRecord.checkKeyguardFlagsChanged();
}
if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0)
- && (mAccessibilityController != null)) {
+ && (mAccessibilityController.hasCallbacks())) {
// No move or resize, but the controller checks for title changes as well
mAccessibilityController.onSomeWindowResizedOrMovedWithCallingUid(
uid, win.getDisplayContent().getDisplayId());
@@ -2465,16 +2472,22 @@ public class WindowManagerService extends IWindowManager.Stub
configChanged = displayContent.updateOrientation();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- final DisplayInfo rotatedDisplayInfo =
- win.mToken.getFixedRotationTransformDisplayInfo();
- if (rotatedDisplayInfo != null) {
- outSurfaceControl.setTransformHint(rotatedDisplayInfo.rotation);
- } else {
- // We have to update the transform hint of display here, but we need to get if from
- // SurfaceFlinger, so set it as rotation of display for most cases, then
- // SurfaceFlinger would still update the transform hint of display in next frame.
- outSurfaceControl.setTransformHint(displayContent.getDisplayInfo().rotation);
- }
+ final DisplayInfo displayInfo = win.getDisplayInfo();
+ int transformHint = displayInfo.rotation;
+ // If the window is on the primary display, use the panel orientation to adjust the
+ // transform hint
+ final boolean isPrimaryDisplay = displayInfo.address != null &&
+ displayInfo.address.equals(mPrimaryDisplayPhysicalAddress);
+ if (isPrimaryDisplay) {
+ transformHint = (transformHint + mPrimaryDisplayOrientation) % 4;
+ }
+ outSurfaceControl.setTransformHint(
+ SurfaceControl.rotationToBufferTransform(transformHint));
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Passing transform hint %d for window %s%s",
+ transformHint, win,
+ isPrimaryDisplay ? " on primary display with orientation "
+ + mPrimaryDisplayOrientation : "");
if (toBeDisplayed && win.mIsWallpaper) {
displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
@@ -2482,7 +2495,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mActivityRecord != null) {
win.mActivityRecord.updateReportedVisibilityLocked();
}
- if (displayPolicy.areSystemBarsForcedShownLw(win)) {
+ if (displayPolicy.areSystemBarsForcedShownLw()) {
result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
}
if (!win.isGoneForLayout()) {
@@ -2528,7 +2541,8 @@ public class WindowManagerService extends IWindowManager.Stub
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (winAnimator.mSurfaceController != null) {
- win.calculateSurfaceBounds(win.getAttrs(), mTmpRect);
+ win.calculateSurfaceBounds(win.getLayoutingAttrs(
+ win.getWindowConfiguration().getRotation()), mTmpRect);
outSurfaceSize.set(mTmpRect.width(), mTmpRect.height());
}
getInsetsSourceControls(win, outActiveControls);
@@ -2566,7 +2580,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
}
- if (mAtmService.getTransitionController().inTransition(win)) {
+ if (win.inTransition()) {
focusMayChange = true;
win.mAnimatingExit = true;
} else if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
@@ -2578,20 +2592,24 @@ public class WindowManagerService extends IWindowManager.Stub
// an exit.
win.mAnimatingExit = true;
} else if (win.mDisplayContent.okToAnimate()
- && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)) {
- // If the wallpaper is currently behind this
- // window, we need to change both of them inside
- // of a transaction to avoid artifacts.
+ && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+ && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+ // If the wallpaper is currently behind this app window, we need to change both of them
+ // inside of a transaction to avoid artifacts.
+ // For NotificationShade, sysui is in charge of running window animation and it updates
+ // the client view visibility only after both NotificationShade and the wallpaper are
+ // hidden. So we don't need to care about exit animation, but can destroy its surface
+ // immediately.
win.mAnimatingExit = true;
} else {
- boolean stopped = win.mActivityRecord != null ? win.mActivityRecord.mAppStopped : true;
+ boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
// We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
// will later actually destroy the surface if we do not do so here. Normally we leave
// this to the exit animation.
win.mDestroying = true;
win.destroySurface(false, stopped);
}
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.onWindowTransition(win, transit);
}
@@ -3095,7 +3113,7 @@ public class WindowManagerService extends IWindowManager.Stub
mSettingsObserver.updateSystemUiSettings(true /* handleChange */);
synchronized (mGlobalLock) {
// force a re-application of focused window sysui visibility on each display.
- mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemUiVisibilityLw);
+ mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemBarAttributes);
}
}
@@ -3840,6 +3858,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ @Override
+ public SurfaceControl mirrorWallpaperSurface(int displayId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ return dc.mWallpaperController.mirrorWallpaperSurface();
+ }
+ }
+
/**
* Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the upper region of the screen based on the vertical dimension
@@ -4105,7 +4131,7 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean pendingRemoteRotation = rotationChanged
&& (displayContent.getDisplayRotation().isWaitingForRemoteRotation()
- || mAtmService.getTransitionController().isCollecting());
+ || displayContent.mTransitionController.isCollecting());
// Even if alwaysSend, we are waiting for a transition or remote to provide
// rotated configuration, so we can't update configuration yet.
if (!pendingRemoteRotation) {
@@ -4225,7 +4251,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void modifyDisplayWindowInsets(int displayId, InsetsState state) {
+ public void updateDisplayWindowRequestedVisibilities(int displayId, InsetsVisibilities vis) {
if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
@@ -4237,7 +4263,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (dc == null || dc.mRemoteInsetsControlTarget == null) {
return;
}
- dc.mRemoteInsetsControlTarget.updateRequestedVisibility(state);
+ dc.mRemoteInsetsControlTarget.setRequestedVisibilities(vis);
dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget);
}
} finally {
@@ -4936,6 +4962,9 @@ public class WindowManagerService extends IWindowManager.Stub
mTaskSnapshotController.systemReady();
mHasWideColorGamutSupport = queryWideColorGamutSupport();
mHasHdrSupport = queryHdrSupport();
+ mPrimaryDisplayOrientation = queryPrimaryDisplayOrientation();
+ mPrimaryDisplayPhysicalAddress =
+ DisplayAddress.fromPhysicalDisplayId(SurfaceControl.getPrimaryPhysicalDisplayId());
UiThread.getHandler().post(mSettingsObserver::loadSettings);
IVrManager vrManager = IVrManager.Stub.asInterface(
ServiceManager.getService(Context.VR_SERVICE));
@@ -4955,6 +4984,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+
+ // Keep logic in sync with SurfaceFlingerProperties.cpp
+ // Consider exposing properties via ISurfaceComposer instead.
private static boolean queryWideColorGamutSupport() {
boolean defaultValue = false;
Optional<Boolean> hasWideColorProp = SurfaceFlingerProperties.has_wide_color_display();
@@ -4995,23 +5027,82 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
+ private static @Surface.Rotation int queryPrimaryDisplayOrientation() {
+ Optional<SurfaceFlingerProperties.primary_display_orientation_values> prop =
+ SurfaceFlingerProperties.primary_display_orientation();
+ if (prop.isPresent()) {
+ switch (prop.get()) {
+ case ORIENTATION_90: return Surface.ROTATION_90;
+ case ORIENTATION_180: return Surface.ROTATION_180;
+ case ORIENTATION_270: return Surface.ROTATION_270;
+ case ORIENTATION_0:
+ default:
+ return Surface.ROTATION_0;
+ }
+ }
+ try {
+ ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService();
+ OptionalDisplayOrientation primaryDisplayOrientation =
+ surfaceFlinger.primaryDisplayOrientation();
+ if (primaryDisplayOrientation != null && primaryDisplayOrientation.specified) {
+ switch (primaryDisplayOrientation.value) {
+ case DisplayOrientation.ORIENTATION_90: return Surface.ROTATION_90;
+ case DisplayOrientation.ORIENTATION_180: return Surface.ROTATION_180;
+ case DisplayOrientation.ORIENTATION_270: return Surface.ROTATION_270;
+ case DisplayOrientation.ORIENTATION_0:
+ default:
+ return Surface.ROTATION_0;
+ }
+ }
+ } catch (Exception e) {
+ // Use default value if we can't talk to config store.
+ }
+ return Surface.ROTATION_0;
+ }
+
+ // Returns an input target which is mapped to the given input token. This can be a WindowState
+ // or an embedded window.
+ @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) {
+ WindowState windowState = mInputToWindowMap.get(inputToken);
+ if (windowState != null) {
+ return windowState;
+ }
+
+ EmbeddedWindowController.EmbeddedWindow embeddedWindow =
+ mEmbeddedWindowController.get(inputToken);
+ if (embeddedWindow != null) {
+ return embeddedWindow;
+ }
+
+ return null;
+ }
+
void reportFocusChanged(IBinder oldToken, IBinder newToken) {
- WindowState lastFocus;
- WindowState newFocus;
+ InputTarget lastTarget;
+ InputTarget newTarget;
synchronized (mGlobalLock) {
- lastFocus = mInputToWindowMap.get(oldToken);
- newFocus = mInputToWindowMap.get(newToken);
- ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastFocus, newFocus);
+ lastTarget = getInputTargetFromToken(oldToken);
+ newTarget = getInputTargetFromToken(newToken);
+ if (newTarget == null && lastTarget == null) {
+ Slog.v(TAG_WM, "Unknown focus tokens, dropping reportFocusChanged");
+ return;
+ }
+
+ mAccessibilityController.onFocusChanged(lastTarget, newTarget);
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Focus changing: %s -> %s", lastTarget, newTarget);
}
- if (newFocus != null) {
- mAnrController.onFocusChanged(newFocus);
- newFocus.reportFocusChangedSerialized(true);
+ // Call WindowState focus change observers
+ WindowState newFocusedWindow = newTarget != null ? newTarget.getWindowState() : null;
+ if (newFocusedWindow != null && newFocusedWindow.mInputChannelToken == newToken) {
+ mAnrController.onFocusChanged(newFocusedWindow);
+ newFocusedWindow.reportFocusChangedSerialized(true);
notifyFocusChanged();
}
- if (lastFocus != null) {
- lastFocus.reportFocusChangedSerialized(false);
+ WindowState lastFocusedWindow = lastTarget != null ? lastTarget.getWindowState() : null;
+ if (lastFocusedWindow != null && lastFocusedWindow.mInputChannelToken == oldToken) {
+ lastFocusedWindow.reportFocusChangedSerialized(false);
}
}
@@ -5363,6 +5454,7 @@ public class WindowManagerService extends IWindowManager.Stub
case LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED: {
synchronized (mGlobalLock) {
final DisplayContent displayContent = (DisplayContent) msg.obj;
+ displayContent.mLayoutAndAssignWindowLayersScheduled = false;
displayContent.layoutAndAssignWindowLayersIfNeeded();
}
break;
@@ -5370,6 +5462,7 @@ public class WindowManagerService extends IWindowManager.Stub
case WINDOW_STATE_BLAST_SYNC_TIMEOUT: {
synchronized (mGlobalLock) {
final WindowState ws = (WindowState) msg.obj;
+ Slog.i(TAG, "Blast sync timeout: " + ws);
ws.immediatelyNotifyBlastSync();
}
break;
@@ -5467,6 +5560,25 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) {
+ if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.setSandboxDisplayApis(sandboxDisplayApis);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/** The global settings only apply to default display. */
private boolean applyForcedPropertiesForDefaultDisplay() {
boolean changed = false;
@@ -5791,7 +5903,9 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
- if (!displayContent.isReady() || !mPolicy.isScreenOn() || !displayContent.okToAnimate()) {
+ if (!displayContent.isReady() || !displayContent.getDisplayPolicy().isScreenOnFully()
+ || displayContent.getDisplayInfo().state == Display.STATE_OFF
+ || !displayContent.okToAnimate()) {
// No need to freeze the screen before the display is ready, if the screen is off,
// or we can't currently animate.
return;
@@ -6089,9 +6203,10 @@ public class WindowManagerService extends IWindowManager.Stub
+ " callers=" + Debug.getCallers(3));
return NAV_BAR_INVALID;
}
- displayContent.performLayout(false /* initial */,
- false /* updateInputWindows */);
- return displayContent.getDisplayPolicy().getNavBarPosition();
+ return displayContent.getDisplayPolicy().navigationBarPosition(
+ displayContent.mBaseDisplayWidth,
+ displayContent.mBaseDisplayHeight,
+ displayContent.getDisplayRotation().getRotation());
}
}
@@ -6394,6 +6509,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mGlobalConfiguration="); pw.println(mRoot.getConfiguration());
pw.print(" mHasPermanentDpad="); pw.println(mHasPermanentDpad);
mRoot.dumpTopFocusedDisplayId(pw);
+ mRoot.dumpDefaultMinSizeOfResizableTask(pw);
mRoot.forAllDisplays(dc -> {
final int displayId = dc.getDisplayId();
final InsetsControlTarget imeLayeringTarget = dc.getImeTarget(IME_TARGET_LAYERING);
@@ -6427,7 +6543,7 @@ public class WindowManagerService extends IWindowManager.Stub
mInputManagerCallback.dump(pw, " ");
mTaskSnapshotController.dump(pw, " ");
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.dump(pw, " ");
}
@@ -7447,7 +7563,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void setMagnificationSpec(int displayId, MagnificationSpec spec) {
synchronized (mGlobalLock) {
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.setMagnificationSpec(displayId, spec);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
@@ -7458,7 +7574,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void setForceShowMagnifiableBounds(int displayId, boolean show) {
synchronized (mGlobalLock) {
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.setForceShowMagnifiableBounds(displayId, show);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
@@ -7469,7 +7585,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void getMagnificationRegion(int displayId, @NonNull Region magnificationRegion) {
synchronized (mGlobalLock) {
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.getMagnificationRegion(displayId, magnificationRegion);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
@@ -7485,7 +7601,7 @@ public class WindowManagerService extends IWindowManager.Stub
return null;
}
MagnificationSpec spec = null;
- if (mAccessibilityController != null) {
+ if (mAccessibilityController.hasCallbacks()) {
spec = mAccessibilityController.getMagnificationSpecForWindow(windowState);
}
if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
@@ -7504,16 +7620,7 @@ public class WindowManagerService extends IWindowManager.Stub
public boolean setMagnificationCallbacks(int displayId,
@Nullable MagnificationCallbacks callbacks) {
synchronized (mGlobalLock) {
- if (mAccessibilityController == null) {
- mAccessibilityController = new AccessibilityController(
- WindowManagerService.this);
- }
- boolean result = mAccessibilityController.setMagnificationCallbacks(
- displayId, callbacks);
- if (!mAccessibilityController.hasCallbacks()) {
- mAccessibilityController = null;
- }
- return result;
+ return mAccessibilityController.setMagnificationCallbacks(displayId, callbacks);
}
}
@@ -7521,17 +7628,8 @@ public class WindowManagerService extends IWindowManager.Stub
public boolean setWindowsForAccessibilityCallback(int displayId,
WindowsForAccessibilityCallback callback) {
synchronized (mGlobalLock) {
- if (mAccessibilityController == null) {
- mAccessibilityController = new AccessibilityController(
- WindowManagerService.this);
- }
- final boolean result =
- mAccessibilityController.setWindowsForAccessibilityCallback(
- displayId, callback);
- if (!mAccessibilityController.hasCallbacks()) {
- mAccessibilityController = null;
- }
- return result;
+ return mAccessibilityController
+ .setWindowsForAccessibilityCallback(displayId, callback);
}
}
@@ -7543,11 +7641,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public IBinder getFocusedWindowToken() {
synchronized (mGlobalLock) {
- WindowState windowState = getFocusedWindowLocked();
- if (windowState != null) {
- return windowState.mClient.asBinder();
- }
- return null;
+ return mAccessibilityController.getFocusedWindowToken();
}
}
@@ -7640,6 +7734,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void registerAppTransitionListener(AppTransitionListener listener) {
synchronized (mGlobalLock) {
getDefaultDisplayContentLocked().mAppTransition.registerListenerLocked(listener);
+ mAtmService.getTransitionController().registerLegacyListener(listener);
}
}
@@ -7703,13 +7798,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void computeWindowsForAccessibility(int displayId) {
- final AccessibilityController accessibilityController;
- synchronized (mGlobalLock) {
- accessibilityController = mAccessibilityController;
- }
- if (accessibilityController != null) {
- accessibilityController.performComputeChangedWindowsNot(displayId, true);
- }
+ mAccessibilityController.performComputeChangedWindowsNot(displayId, true);
}
@Override
@@ -7783,7 +7872,7 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowState currentFocus = displayContent.mCurrentFocus;
if (currentFocus != null && currentFocus.mSession.mUid == uid
&& currentFocus.mSession.mPid == pid) {
- return true;
+ return currentFocus.canBeImeTarget();
}
}
return false;
@@ -7942,30 +8031,37 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public String getImeControlTargetNameForLogging(int displayId) {
+ public ImeTargetInfo onToggleImeRequested(boolean show, IBinder focusedToken,
+ IBinder requestToken, int displayId) {
+ final String focusedWindowName;
+ final String requestWindowName;
+ final String imeControlTargetName;
+ final String imeLayerTargetName;
synchronized (mGlobalLock) {
+ final WindowState focusedWin = mWindowMap.get(focusedToken);
+ focusedWindowName = focusedWin != null ? focusedWin.getName() : "null";
+ final WindowState requestWin = mWindowMap.get(requestToken);
+ requestWindowName = requestWin != null ? requestWin.getName() : "null";
final DisplayContent dc = mRoot.getDisplayContent(displayId);
- if (dc == null) {
- return null;
- }
- final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_CONTROL);
- if (target == null) {
- return null;
- }
- final WindowState win = target.getWindow();
- return win != null ? win.getName() : target.toString();
- }
- }
-
- @Override
- public String getImeTargetNameForLogging(int displayId) {
- synchronized (mGlobalLock) {
- final DisplayContent dc = mRoot.getDisplayContent(displayId);
- if (dc == null || dc.getImeTarget(IME_TARGET_LAYERING) == null) {
- return null;
+ if (dc != null) {
+ final InsetsControlTarget controlTarget = dc.getImeTarget(IME_TARGET_CONTROL);
+ if (controlTarget != null) {
+ final WindowState w = InsetsControlTarget.asWindowOrNull(controlTarget);
+ imeControlTargetName = w != null ? w.getName() : controlTarget.toString();
+ } else {
+ imeControlTargetName = "null";
+ }
+ final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_LAYERING);
+ imeLayerTargetName = target != null ? target.getWindow().getName() : "null";
+ if (show) {
+ dc.onShowImeRequested();
+ }
+ } else {
+ imeControlTargetName = imeLayerTargetName = "no-display";
}
- return dc.getImeTarget(IME_TARGET_LAYERING).getWindow().getName();
}
+ return new ImeTargetInfo(focusedWindowName, requestWindowName, imeControlTargetName,
+ imeLayerTargetName);
}
@Override
@@ -8163,11 +8259,20 @@ public class WindowManagerService extends IWindowManager.Stub
// This could prevent if there is no container animation, we still have to apply the
// pending transaction and exit waiting.
mAnimator.mNotifyWhenNoAnimation = true;
+ boolean animateStarting = false;
while (timeoutRemaining > 0) {
+ // Waiting until all starting windows has finished animating.
+ animateStarting = !mAtmService.getTransitionController().isShellTransitionsEnabled()
+ && mRoot.forAllActivities(ActivityRecord::hasStartingWindow);
boolean isAnimating = mAnimator.isAnimationScheduled()
- || mRoot.isAnimating(TRANSITION | CHILDREN, ANIMATION_TYPE_ALL);
+ || mRoot.isAnimating(TRANSITION | CHILDREN, ANIMATION_TYPE_ALL)
+ || animateStarting;
if (!isAnimating) {
- break;
+ // isAnimating is a legacy transition query and will be removed, so also add
+ // a check for whether this is in a shell-transition when not using legacy.
+ if (!mAtmService.getTransitionController().inTransition()) {
+ break;
+ }
}
long startTime = System.currentTimeMillis();
try {
@@ -8181,13 +8286,14 @@ public class WindowManagerService extends IWindowManager.Stub
WindowContainer animatingContainer;
animatingContainer = mRoot.getAnimatingContainer(TRANSITION | CHILDREN,
ANIMATION_TYPE_ALL);
- if (mAnimator.isAnimationScheduled() || animatingContainer != null) {
+ if (mAnimator.isAnimationScheduled() || animatingContainer != null || animateStarting) {
Slog.w(TAG, "Timed out waiting for animations to complete,"
+ " animatingContainer=" + animatingContainer
+ " animationType=" + SurfaceAnimator.animationTypeToString(
animatingContainer != null
? animatingContainer.mSurfaceAnimator.getAnimationType()
- : SurfaceAnimator.ANIMATION_TYPE_NONE));
+ : SurfaceAnimator.ANIMATION_TYPE_NONE)
+ + " animateStarting=" + animateStarting);
}
}
}
@@ -8229,11 +8335,11 @@ public class WindowManagerService extends IWindowManager.Stub
displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
true /* includingParents */);
}
- handleTaskFocusChange(touchedWindow.getTask());
+ handleTaskFocusChange(touchedWindow.getTask(), touchedWindow.mActivityRecord);
}
@VisibleForTesting
- void handleTaskFocusChange(Task task) {
+ void handleTaskFocusChange(Task task, ActivityRecord touchedActivity) {
if (task == null) {
return;
}
@@ -8252,7 +8358,24 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- mAtmService.setFocusedTask(task.mTaskId);
+ mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
+ }
+
+ /**
+ * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
+ */
+ private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
+ if ((flags & FLAG_SLIPPERY) == 0) {
+ return flags;
+ }
+ final int permissionResult = mContext.checkPermission(
+ android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid);
+ if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName
+ + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission");
+ return flags & ~FLAG_SLIPPERY;
+ }
+ return flags;
}
/**
@@ -8276,11 +8399,11 @@ public class WindowManagerService extends IWindowManager.Stub
clientChannel = win.openInputChannel();
mEmbeddedWindowController.add(clientChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
- name = win.getName();
+ name = win.toString();
}
updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
- name, applicationHandle, flags, privateFlags, type, null /* region */);
+ name, applicationHandle, flags, privateFlags, type, null /* region */, window);
clientChannel.copyTo(outInputChannel);
}
@@ -8288,13 +8411,16 @@ public class WindowManagerService extends IWindowManager.Stub
private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid,
int displayId, SurfaceControl surface, String name,
InputApplicationHandle applicationHandle, int flags,
- int privateFlags, int type, Region region) {
+ int privateFlags, int type, Region region, IWindow window) {
InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId);
h.token = channelToken;
+ h.setWindowToken(window);
h.name = name;
+ flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
+
final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
- | LayoutParams.FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
+ | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags;
h.layoutParamsType = type;
h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
@@ -8341,12 +8467,12 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.e(TAG, "Couldn't find window for provided channelToken.");
return;
}
- name = win.getName();
+ name = win.toString();
applicationHandle = win.getApplicationHandle();
}
updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name,
- applicationHandle, flags, privateFlags, win.mWindowType, region);
+ applicationHandle, flags, privateFlags, win.mWindowType, region, win.mClient);
}
/** Return whether layer tracing is enabled */
@@ -8504,10 +8630,9 @@ public class WindowManagerService extends IWindowManager.Stub
SurfaceControl.Transaction t = mTransactionFactory.get();
final int displayId = embeddedWindow.mDisplayId;
if (grantFocus) {
- t.setFocusedWindow(inputToken, embeddedWindow.getName(), displayId).apply();
+ t.setFocusedWindow(inputToken, embeddedWindow.toString(), displayId).apply();
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
- "Focus request " + embeddedWindow.getName(),
- "reason=grantEmbeddedWindowFocus(true)");
+ "Focus request " + embeddedWindow, "reason=grantEmbeddedWindowFocus(true)");
} else {
// Search for a new focus target
DisplayContent displayContent = mRoot.getDisplayContent(displayId);
@@ -8516,18 +8641,18 @@ public class WindowManagerService extends IWindowManager.Stub
if (newFocusTarget == null) {
ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for "
+ "win=%s dropped since no candidate was found",
- embeddedWindow.getName());
+ embeddedWindow);
return;
}
t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(),
- inputToken, embeddedWindow.getName(),
+ inputToken, embeddedWindow.toString(),
displayId).apply();
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
"Transfer focus request " + newFocusTarget,
"reason=grantEmbeddedWindowFocus(false)");
}
ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
- embeddedWindow.getName(), grantFocus);
+ embeddedWindow, grantFocus);
}
}
@@ -8556,24 +8681,24 @@ public class WindowManagerService extends IWindowManager.Stub
}
SurfaceControl.Transaction t = mTransactionFactory.get();
if (grantFocus) {
- t.requestFocusTransfer(targetInputToken, embeddedWindow.getName(),
+ t.requestFocusTransfer(targetInputToken, embeddedWindow.toString(),
hostWindow.mInputChannel.getToken(),
hostWindow.getName(),
hostWindow.getDisplayId()).apply();
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
- "Transfer focus request " + embeddedWindow.getName(),
+ "Transfer focus request " + embeddedWindow,
"reason=grantEmbeddedWindowFocus(true)");
} else {
t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), hostWindow.getName(),
targetInputToken,
- embeddedWindow.getName(),
+ embeddedWindow.toString(),
hostWindow.getDisplayId()).apply();
EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
"Transfer focus request " + hostWindow,
"reason=grantEmbeddedWindowFocus(false)");
}
ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s",
- embeddedWindow.getName(), grantFocus);
+ embeddedWindow, grantFocus);
}
}
@@ -8625,7 +8750,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (win.mActivityRecord == null || !win.mActivityRecord.isState(
- Task.ActivityState.RESUMED)) {
+ ActivityRecord.State.RESUMED)) {
mDisplayHashController.sendDisplayHashError(callback,
DISPLAY_HASH_ERROR_MISSING_WINDOW);
return;
@@ -8681,4 +8806,41 @@ public class WindowManagerService extends IWindowManager.Stub
return snapshot != null && snapshot.hasImeSurface();
}
}
+
+ @Override
+ public int getImeDisplayId() {
+ // TODO(b/189805422): Add a toast to notify users that IMS may get extra
+ // onConfigurationChanged callback when perDisplayFocus is enabled.
+ // Enabling perDisplayFocus means that we track focus on each display, so we don't have
+ // the "top focus" display and getTopFocusedDisplayContent returns the default display
+ // as the fallback. It leads to InputMethodService receives an extra onConfiguration
+ // callback when InputMethodService move from a secondary display to another display
+ // with the same display metrics because InputMethodService will always associate with
+ // the ImeContainer on the default display in onCreate and receive a configuration update
+ // to match default display ImeContainer and then receive another configuration update
+ // from attachToWindowToken.
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getTopFocusedDisplayContent();
+ return dc.getImePolicy() == DISPLAY_IME_POLICY_LOCAL ? dc.getDisplayId()
+ : DEFAULT_DISPLAY;
+ }
+ }
+
+ @Override
+ public void setTaskTransitionSpec(TaskTransitionSpec spec) {
+ if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "setTaskTransitionSpec()")) {
+ throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+ }
+
+ mTaskTransitionSpec = spec;
+ }
+
+ @Override
+ public void clearTaskTransitionSpec() {
+ if (!checkCallingPermission(MANAGE_ACTIVITY_TASKS, "clearTaskTransitionSpec()")) {
+ throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+ }
+
+ mTaskTransitionSpec = null;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a82a478f2a4f..6970c7942a50 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -16,13 +16,23 @@
package com.android.server.wm;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.isStartResultSuccessful;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
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_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
@@ -33,7 +43,12 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.IApplicationThread;
import android.app.WindowConfiguration;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -42,14 +57,21 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizerController;
+import android.window.ITaskFragmentOrganizer;
+import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController;
+import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
import android.window.IWindowContainerTransactionCallback;
import android.window.IWindowOrganizerController;
+import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
@@ -63,7 +85,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Server side implementation for the interface for organizing windows
@@ -95,15 +117,27 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final TaskOrganizerController mTaskOrganizerController;
final DisplayAreaOrganizerController mDisplayAreaOrganizerController;
+ final TaskFragmentOrganizerController mTaskFragmentOrganizerController;
- final TransitionController mTransitionController;
+ TransitionController mTransitionController;
+ /**
+ * A Map which manages the relationship between
+ * {@link TaskFragmentCreationParams#getFragmentToken()} and {@link TaskFragment}
+ */
+ @VisibleForTesting
+ final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
mTaskOrganizerController = new TaskOrganizerController(mService);
mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService);
- mTransitionController = new TransitionController(atm);
+ mTaskFragmentOrganizerController = new TaskFragmentOrganizerController(atm);
+ }
+
+ void setWindowManager(WindowManagerService wms) {
+ mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController);
+ mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
}
TransitionController getTransitionController() {
@@ -122,14 +156,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override
public void applyTransaction(WindowContainerTransaction t) {
- enforceTaskPermission("applyTransaction()");
if (t == null) {
- throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
+ throw new IllegalArgumentException("Null transaction passed to applyTransaction");
}
+ enforceTaskPermission("applyTransaction()", t);
+ final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- applyTransaction(t, -1 /*syncId*/, null /*transition*/);
+ applyTransaction(t, -1 /*syncId*/, null /*transition*/, caller);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -139,10 +174,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override
public int applySyncTransaction(WindowContainerTransaction t,
IWindowContainerTransactionCallback callback) {
- enforceTaskPermission("applySyncTransaction()");
if (t == null) {
throw new IllegalArgumentException("Null transaction passed to applySyncTransaction");
}
+ enforceTaskPermission("applySyncTransaction()", t);
+ final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
@@ -162,7 +198,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (callback != null) {
syncId = startSyncWithOrganizer(callback);
}
- applyTransaction(t, syncId, null /*transition*/);
+ applyTransaction(t, syncId, null /*transition*/, caller);
if (syncId >= 0) {
setSyncReady(syncId);
}
@@ -177,6 +213,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
public IBinder startTransition(int type, @Nullable IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
enforceTaskPermission("startTransition()");
+ final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
@@ -196,7 +233,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
throw new IllegalArgumentException("Can't use legacy transitions in"
+ " compatibility mode with no WCT.");
}
- applyTransaction(t, -1 /* syncId */, null);
+ applyTransaction(t, -1 /* syncId */, null, caller);
return null;
}
transition = mTransitionController.createTransition(type);
@@ -205,9 +242,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (t == null) {
t = new WindowContainerTransaction();
}
- applyTransaction(t, -1 /*syncId*/, transition);
+ applyTransaction(t, -1 /*syncId*/, transition, caller);
if (needsSetReady) {
- transition.setReady();
+ transition.setAllReady();
}
return transition;
}
@@ -217,10 +254,47 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
@Override
+ public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
+ @NonNull IWindowContainerTransactionCallback callback,
+ @NonNull WindowContainerTransaction t) {
+ enforceTaskPermission("startLegacyTransition()");
+ final CallerInfo caller = new CallerInfo();
+ final long ident = Binder.clearCallingIdentity();
+ int syncId;
+ try {
+ synchronized (mGlobalLock) {
+ if (type < 0) {
+ throw new IllegalArgumentException("Can't create transition with no type");
+ }
+ if (mTransitionController.getTransitionPlayer() != null) {
+ throw new IllegalArgumentException("Can't use legacy transitions in"
+ + " when shell transitions are enabled.");
+ }
+ final DisplayContent dc =
+ mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
+ if (dc.mAppTransition.isTransitionSet()) {
+ // a transition already exists, so the callback probably won't be called.
+ return -1;
+ }
+ adapter.setCallingPidUid(caller.mPid, caller.mUid);
+ dc.prepareAppTransition(type);
+ dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */);
+ syncId = startSyncWithOrganizer(callback);
+ applyTransaction(t, syncId, null /* transition */, caller);
+ setSyncReady(syncId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return syncId;
+ }
+
+ @Override
public int finishTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t,
@Nullable IWindowContainerTransactionCallback callback) {
enforceTaskPermission("finishTransition()");
+ final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
@@ -231,7 +305,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// apply the incoming transaction before finish in case it alters the visibility
// of the participants.
if (t != null) {
- applyTransaction(t, syncId, null /*transition*/);
+ applyTransaction(t, syncId, null /*transition*/, caller);
}
getTransitionController().finishTransition(transitionToken);
if (syncId >= 0) {
@@ -247,17 +321,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
/**
* @param syncId If non-null, this will be a sync-transaction.
* @param transition A transition to collect changes into.
+ * @param caller Info about the calling process.
*/
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
- @Nullable Transition transition) {
+ @Nullable Transition transition, @NonNull CallerInfo caller) {
int effects = 0;
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
mService.deferWindowLayout();
+ mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
try {
if (transition != null) {
// First check if we have a display rotation transition and if so, update it.
final DisplayContent dc = DisplayRotation.getDisplayFromTransition(transition);
- if (dc != null && transition.mChanges.get(dc).mRotation != dc.getRotation()) {
+ if (dc != null && transition.mChanges.get(dc).hasChanged(dc)) {
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once
// wallpaper handling is synchronized.
@@ -303,7 +379,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final boolean isInLockTaskMode = mService.isInLockTaskMode();
for (int i = 0; i < hopSize; ++i) {
effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,
- isInLockTaskMode);
+ isInLockTaskMode, caller, t.getErrorCallbackToken(),
+ t.getTaskFragmentOrganizer());
}
}
// Queue-up bounds-change transactions for tasks which are now organized. Do
@@ -341,6 +418,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
task.setMainWindowSizeChangeTransaction(sft);
}
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+ mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
// Already calls ensureActivityConfig
mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
@@ -362,6 +440,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED);
}
} finally {
+ mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
mService.continueWindowLayout();
}
}
@@ -389,6 +468,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
container.onRequestedOverrideConfigurationChanged(c);
}
effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ if (windowMask != 0 && container.isEmbedded()) {
+ // Changing bounds of the embedded TaskFragments may result in lifecycle changes.
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
}
if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
if (container.setFocusable(change.getFocusable())) {
@@ -402,7 +485,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
throw new UnsupportedOperationException("Not supported to set multi-window"
+ " windowing mode during locked task mode.");
}
+
+ final int prevMode = container.getWindowingMode();
container.setWindowingMode(windowingMode);
+ if (prevMode != container.getWindowingMode()) {
+ // The activity in the container may become focusable or non-focusable due to
+ // windowing modes changes (such as entering or leaving pinned windowing mode),
+ // so also apply the lifecycle effects to this transaction.
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
}
return effects;
}
@@ -458,7 +549,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
- int syncId, @Nullable Transition transition, boolean isInLockTaskMode) {
+ int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
+ @NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
+ @Nullable ITaskFragmentOrganizer organizer) {
final int type = hop.getType();
switch (type) {
case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: {
@@ -475,38 +568,143 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: {
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
final Task task = wc != null ? wc.asTask() : null;
+ final boolean clearRoot = hop.getToTop();
if (task == null) {
throw new IllegalArgumentException("Cannot set non-task as launch root: " + wc);
} else if (!task.mCreatedByOrganizer) {
throw new UnsupportedOperationException(
"Cannot set non-organized task as adjacent flag root: " + wc);
- } else if (task.mAdjacentTask == null) {
+ } else if (task.getAdjacentTaskFragment() == null && !clearRoot) {
throw new UnsupportedOperationException(
"Cannot set non-adjacent task as adjacent flag root: " + wc);
}
- final boolean clearRoot = hop.getToTop();
task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task);
break;
}
- case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: {
effects |= setAdjacentRootsHierarchyOp(hop);
break;
- }
- // The following operations may change task order so they are skipped while in lock task
- // mode. The above operations are still allowed because they don't move tasks. And it may
- // be necessary such as clearing launch root after entering lock task mode.
- if (isInLockTaskMode) {
- Slog.w(TAG, "Skip applying hierarchy operation " + hop + " while in lock task mode");
- return effects;
+ }
+ case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: {
+ final TaskFragmentCreationParams taskFragmentCreationOptions =
+ hop.getTaskFragmentCreationOptions();
+ createTaskFragment(taskFragmentCreationOptions, errorCallbackToken, caller);
+ break;
+ }
+ case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: {
+ final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+ if (wc == null || !wc.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+ break;
+ }
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment == null || taskFragment.asTask() != null) {
+ throw new IllegalArgumentException(
+ "Can only delete organized TaskFragment, but not Task.");
+ }
+ if (isInLockTaskMode) {
+ final ActivityRecord bottomActivity = taskFragment.getActivity(
+ a -> !a.finishing, false /* traverseTopToBottom */);
+ if (bottomActivity != null
+ && mService.getLockTaskController().activityBlockedFromFinish(
+ bottomActivity)) {
+ Slog.w(TAG, "Skip removing TaskFragment due in lock task mode.");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
+ new IllegalStateException(
+ "Not allow to delete task fragment in lock task mode."));
+ break;
+ }
+ }
+ effects |= deleteTaskFragment(taskFragment, errorCallbackToken);
+ break;
+ }
+ case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: {
+ final IBinder fragmentToken = hop.getContainer();
+ if (!mLaunchTaskFragments.containsKey(fragmentToken)) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to operate with invalid fragment token");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ break;
+ }
+ final Intent activityIntent = hop.getActivityIntent();
+ final Bundle activityOptions = hop.getLaunchOptions();
+ final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken);
+ final int result = mService.getActivityStartController()
+ .startActivityInTaskFragment(tf, activityIntent, activityOptions,
+ hop.getCallingActivity(), caller.mUid, caller.mPid);
+ if (!isStartResultSuccessful(result)) {
+ sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(),
+ errorCallbackToken,
+ convertStartFailureToThrowable(result, activityIntent));
+ }
+ break;
+ }
+ case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
+ final IBinder fragmentToken = hop.getNewParent();
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer());
+ if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to operate with invalid fragment token or activity.");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ break;
+ }
+ activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ break;
+ }
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: {
+ final IBinder fragmentToken = hop.getContainer();
+ final IBinder adjacentFragmentToken = hop.getAdjacentRoot();
+ final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken);
+ final TaskFragment tf2 = adjacentFragmentToken != null
+ ? mLaunchTaskFragments.get(adjacentFragmentToken)
+ : null;
+ if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to set adjacent on invalid fragment tokens");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ break;
+ }
+ tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+
+ final Bundle bundle = hop.getLaunchOptions();
+ final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams =
+ bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams(
+ bundle) : null;
+ if (adjacentParams == null) {
+ break;
+ }
+
+ tf1.setDelayLastActivityRemoval(
+ adjacentParams.shouldDelayPrimaryLastActivityRemoval());
+ if (tf2 != null) {
+ tf2.setDelayLastActivityRemoval(
+ adjacentParams.shouldDelaySecondaryLastActivityRemoval());
+ }
+ break;
+ }
+ default: {
+ // The other operations may change task order so they are skipped while in lock
+ // task mode. The above operations are still allowed because they don't move
+ // tasks. And it may be necessary such as clearing launch root after entering
+ // lock task mode.
+ if (isInLockTaskMode) {
+ Slog.w(TAG, "Skip applying hierarchy operation " + hop
+ + " while in lock task mode");
+ return effects;
+ }
+ }
}
switch (type) {
- case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT:
+ case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: {
effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
break;
+ }
case HIERARCHY_OP_TYPE_REORDER:
- case HIERARCHY_OP_TYPE_REPARENT:
+ case HIERARCHY_OP_TYPE_REPARENT: {
final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
if (wc == null || !wc.isAttached()) {
Slog.e(TAG, "Attempt to operate on detached container: " + wc);
@@ -536,13 +734,76 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
break;
- case HIERARCHY_OP_TYPE_LAUNCH_TASK:
+ }
+ case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
+ mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
+ "launchTask HierarchyOp");
final Bundle launchOpts = hop.getLaunchOptions();
final int taskId = launchOpts.getInt(
WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
- mService.startActivityFromRecents(taskId, launchOpts);
+ final SafeActivityOptions safeOptions =
+ SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
+ final Integer[] starterResult = {null};
+ // startActivityFromRecents should not be called in lock.
+ mService.mH.post(() -> {
+ try {
+ starterResult[0] = mService.mTaskSupervisor.startActivityFromRecents(
+ caller.mPid, caller.mUid, taskId, safeOptions);
+ } catch (Throwable t) {
+ starterResult[0] = ActivityManager.START_CANCELED;
+ Slog.w(TAG, t);
+ }
+ synchronized (mGlobalLock) {
+ mGlobalLock.notifyAll();
+ }
+ });
+ while (starterResult[0] == null) {
+ try {
+ mGlobalLock.wait();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ break;
+ }
+ case HIERARCHY_OP_TYPE_PENDING_INTENT: {
+ String resolvedType = hop.getActivityIntent() != null
+ ? hop.getActivityIntent().resolveTypeIfNeeded(
+ mService.mContext.getContentResolver())
+ : null;
+
+ Bundle options = null;
+ if (hop.getPendingIntent().isActivity()) {
+ // Set the context display id as preferred for this activity launches, so that
+ // it can land on caller's display. Or just brought the task to front at the
+ // display where it was on since it has higher preference.
+ ActivityOptions activityOptions = hop.getLaunchOptions() != null
+ ? new ActivityOptions(hop.getLaunchOptions())
+ : ActivityOptions.makeBasic();
+ activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
+ options = activityOptions.toBundle();
+ }
+
+ mService.mAmInternal.sendIntentSender(hop.getPendingIntent().getTarget(),
+ hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
+ hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
+ null /* requiredPermission */, options);
break;
+ }
+ case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: {
+ final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
+ final WindowContainer newParent = hop.getNewParent() != null
+ ? WindowContainer.fromBinder(hop.getNewParent())
+ : null;
+ if (oldParent == null || !oldParent.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + oldParent);
+ break;
+ }
+ reparentTaskFragment(oldParent, newParent, errorCallbackToken);
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ break;
+ }
}
return effects;
}
@@ -661,24 +922,31 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// We want to collect the tasks first before re-parenting to avoid array shifting on us.
final ArrayList<Task> tasksToReparent = new ArrayList<>();
- currentParent.forAllTasks((Consumer<Task>) (task) -> {
+ currentParent.forAllTasks((Function<Task, Boolean>) task -> {
Slog.i(TAG, " Processing task=" + task);
- if (task.mCreatedByOrganizer
- || task.getParent() != finalCurrentParent) {
+ final boolean reparent;
+ if (task.mCreatedByOrganizer || task.getParent() != finalCurrentParent) {
// We only care about non-organized task that are direct children of the thing we
// are reparenting from.
- return;
+ return false;
}
if (newParentInMultiWindow && !task.supportsMultiWindowInDisplayArea(newParentTda)) {
Slog.e(TAG, "reparentChildrenTasksHierarchyOp non-resizeable task to multi window,"
+ " task=" + task);
- return;
+ return false;
+ }
+ if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())
+ || !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
+ return false;
}
- if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) return;
- if (!ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) return;
- tasksToReparent.add(task);
- }, !hop.getToTop());
+ if (hop.getToTop()) {
+ tasksToReparent.add(0, task);
+ } else {
+ tasksToReparent.add(task);
+ }
+ return hop.getReparentTopOnly() && tasksToReparent.size() == 1;
+ });
final int count = tasksToReparent.size();
for (int i = 0; i < count; ++i) {
@@ -704,19 +972,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
- final Task root1 = WindowContainer.fromBinder(hop.getContainer()).asTask();
- final Task root2 = WindowContainer.fromBinder(hop.getAdjacentRoot()).asTask();
+ final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+ final TaskFragment root2 =
+ WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment();
if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
+ " organizer root1=" + root1 + " root2=" + root2);
}
- root1.setAdjacentTask(root2);
+ root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether());
return TRANSACT_EFFECTS_LIFECYCLE;
}
private void sanitizeWindowContainer(WindowContainer wc) {
- if (!(wc instanceof Task) && !(wc instanceof DisplayArea)) {
- throw new RuntimeException("Invalid token in task or displayArea transaction");
+ if (!(wc instanceof TaskFragment) && !(wc instanceof DisplayArea)) {
+ throw new RuntimeException("Invalid token in task fragment or displayArea transaction");
}
}
@@ -747,6 +1016,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return mDisplayAreaOrganizerController;
}
+ @Override
+ public ITaskFragmentOrganizerController getTaskFragmentOrganizerController() {
+ return mTaskFragmentOrganizerController;
+ }
+
@VisibleForTesting
int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
int id = mService.mWindowManager.mSyncEngine.startSyncSet(this);
@@ -785,17 +1059,259 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@Override
public void registerTransitionPlayer(ITransitionPlayer player) {
enforceTaskPermission("registerTransitionPlayer()");
+ final int callerPid = Binder.getCallingPid();
+ final int callerUid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- mTransitionController.registerTransitionPlayer(player);
+ final WindowProcessController wpc =
+ mService.getProcessController(callerPid, callerUid);
+ IApplicationThread appThread = null;
+ if (wpc != null) {
+ appThread = wpc.getThread();
+ }
+ mTransitionController.registerTransitionPlayer(player, appThread);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ @Override
+ public ITransitionMetricsReporter getTransitionMetricsReporter() {
+ return mTransitionController.mTransitionMetricsReporter;
+ }
+
+ /** Whether the configuration changes are important to report back to an organizer. */
+ static boolean configurationsAreEqualForOrganizer(
+ Configuration newConfig, @Nullable Configuration oldConfig) {
+ if (oldConfig == null) {
+ return false;
+ }
+ int cfgChanges = newConfig.diff(oldConfig);
+ final int winCfgChanges = (cfgChanges & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
+ ? (int) newConfig.windowConfiguration.diff(oldConfig.windowConfiguration,
+ true /* compareUndefined */) : 0;
+ if ((winCfgChanges & CONTROLLABLE_WINDOW_CONFIGS) == 0) {
+ cfgChanges &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+ }
+ return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
+ }
+
private void enforceTaskPermission(String func) {
mService.enforceTaskPermission(func);
}
+
+ private void enforceTaskPermission(String func, WindowContainerTransaction t) {
+ if (t == null || t.getTaskFragmentOrganizer() == null) {
+ enforceTaskPermission(func);
+ return;
+ }
+
+ // Apps may not have the permission to manage Tasks, but we are allowing apps to manage
+ // TaskFragments belonging to their own Task.
+ enforceOperationsAllowedForTaskFragmentOrganizer(func, t);
+ }
+
+ /**
+ * Makes sure that the transaction only contains operations that are allowed for the
+ * {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
+ */
+ private void enforceOperationsAllowedForTaskFragmentOrganizer(
+ String func, WindowContainerTransaction t) {
+ final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
+
+ // Configuration changes
+ final Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+ t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();
+ // Only allow to apply changes to TaskFragment that is created by this organizer.
+ enforceTaskFragmentOrganized(func, WindowContainer.fromBinder(entry.getKey()),
+ organizer);
+ }
+
+ // Hierarchy changes
+ final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
+ for (int i = hops.size() - 1; i >= 0; i--) {
+ final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
+ final int type = hop.getType();
+ // Check for each type of the operations that are allowed for TaskFragmentOrganizer.
+ switch (type) {
+ case HIERARCHY_OP_TYPE_REORDER:
+ case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT:
+ enforceTaskFragmentOrganized(func,
+ WindowContainer.fromBinder(hop.getContainer()), organizer);
+ break;
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
+ enforceTaskFragmentOrganized(func,
+ WindowContainer.fromBinder(hop.getContainer()), organizer);
+ enforceTaskFragmentOrganized(func,
+ WindowContainer.fromBinder(hop.getAdjacentRoot()),
+ organizer);
+ break;
+ case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
+ // We are allowing organizer to create TaskFragment. We will check the
+ // ownerToken in #createTaskFragment, and trigger error callback if that is not
+ // valid.
+ case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
+ // We are allowing organizer to start/reparent activity to a TaskFragment it
+ // created, or set two TaskFragments adjacent to each other. Nothing to check
+ // here because the TaskFragment may not be created yet, but will be created in
+ // the same transaction.
+ break;
+ case HIERARCHY_OP_TYPE_REPARENT_CHILDREN:
+ enforceTaskFragmentOrganized(func,
+ WindowContainer.fromBinder(hop.getContainer()), organizer);
+ if (hop.getNewParent() != null) {
+ enforceTaskFragmentOrganized(func,
+ WindowContainer.fromBinder(hop.getNewParent()),
+ organizer);
+ }
+ break;
+ default:
+ // Other types of hierarchy changes are not allowed.
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " trying to apply a hierarchy change that is not allowed for"
+ + " TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+ }
+
+ private void enforceTaskFragmentOrganized(String func, @Nullable WindowContainer wc,
+ ITaskFragmentOrganizer organizer) {
+ if (wc == null) {
+ Slog.e(TAG, "Attempt to operate on window that no longer exists");
+ return;
+ }
+
+ final TaskFragment tf = wc.asTaskFragment();
+ if (tf == null || !tf.hasTaskFragmentOrganizer(organizer)) {
+ String msg = "Permission Denial: " + func + " from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid() + " trying to modify window container not"
+ + " belonging to the TaskFragmentOrganizer=" + organizer;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+ }
+
+ void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
+ @Nullable IBinder errorCallbackToken, @NonNull CallerInfo caller) {
+ final ActivityRecord ownerActivity =
+ ActivityRecord.forTokenLocked(creationParams.getOwnerToken());
+ final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface(
+ creationParams.getOrganizer().asBinder());
+
+ if (ownerActivity == null || ownerActivity.getTask() == null) {
+ final Throwable exception =
+ new IllegalArgumentException("Not allowed to operate with invalid ownerToken");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ return;
+ }
+ if (!ownerActivity.isResizeable()) {
+ final IllegalArgumentException exception = new IllegalArgumentException("Not allowed"
+ + " to operate with non-resizable owner Activity");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ return;
+ }
+ // The ownerActivity has to belong to the same app as the target Task.
+ if (ownerActivity.getTask().effectiveUid != ownerActivity.getUid()
+ || ownerActivity.getTask().effectiveUid != caller.mUid) {
+ final Throwable exception =
+ new IllegalArgumentException("Not allowed to operate with the ownerToken while "
+ + "the root activity of the target task belong to the different app");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ return;
+ }
+ final TaskFragment taskFragment = new TaskFragment(mService,
+ creationParams.getFragmentToken(), true /* createdByOrganizer */);
+ // Set task fragment organizer immediately, since it might have to be notified about further
+ // actions.
+ taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
+ ownerActivity.getUid(), ownerActivity.info.processName);
+ ownerActivity.getTask().addChild(taskFragment, POSITION_TOP);
+ taskFragment.setWindowingMode(creationParams.getWindowingMode());
+ taskFragment.setBounds(creationParams.getInitialBounds());
+ mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
+ }
+
+ void reparentTaskFragment(@NonNull WindowContainer oldParent,
+ @Nullable WindowContainer newParent, @Nullable IBinder errorCallbackToken) {
+ WindowContainer parent = newParent;
+ if (parent == null && oldParent.asTaskFragment() != null) {
+ parent = oldParent.asTaskFragment().getTask();
+ }
+ if (parent == null) {
+ final Throwable exception =
+ new IllegalArgumentException("Not allowed to operate with invalid container");
+ sendTaskFragmentOperationFailure(oldParent.asTaskFragment().getTaskFragmentOrganizer(),
+ errorCallbackToken, exception);
+ return;
+ }
+ while (oldParent.hasChild()) {
+ oldParent.getChildAt(0).reparent(parent, POSITION_TOP);
+ }
+ }
+
+ private int deleteTaskFragment(@NonNull TaskFragment taskFragment,
+ @Nullable IBinder errorCallbackToken) {
+ final int index = mLaunchTaskFragments.indexOfValue(taskFragment);
+ if (index < 0) {
+ final Throwable exception =
+ new IllegalArgumentException("Not allowed to operate with invalid "
+ + "taskFragment");
+ sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(),
+ errorCallbackToken, exception);
+ return 0;
+ }
+ mLaunchTaskFragments.removeAt(index);
+ taskFragment.remove(true /* withTransition */, "deleteTaskFragment");
+ return TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
+ @Nullable
+ TaskFragment getTaskFragment(IBinder tfToken) {
+ return mLaunchTaskFragments.get(tfToken);
+ }
+
+ static class CallerInfo {
+ final int mPid;
+ final int mUid;
+
+ CallerInfo() {
+ mPid = Binder.getCallingPid();
+ mUid = Binder.getCallingUid();
+ }
+ }
+
+ void sendTaskFragmentOperationFailure(@NonNull ITaskFragmentOrganizer organizer,
+ @Nullable IBinder errorCallbackToken, @NonNull Throwable exception) {
+ if (organizer == null) {
+ throw new IllegalArgumentException("Not allowed to operate with invalid organizer");
+ }
+ mService.mTaskFragmentOrganizerController
+ .onTaskFragmentError(organizer, errorCallbackToken, exception);
+ }
+
+ private Throwable convertStartFailureToThrowable(int result, Intent intent) {
+ switch (result) {
+ case ActivityManager.START_INTENT_NOT_RESOLVED:
+ case ActivityManager.START_CLASS_NOT_FOUND:
+ return new ActivityNotFoundException("No Activity found to handle " + intent);
+ case ActivityManager.START_PERMISSION_DENIED:
+ return new SecurityException("Permission denied and not allowed to start activity "
+ + intent);
+ case ActivityManager.START_CANCELED:
+ return new AndroidRuntimeException("Activity could not be started for " + intent
+ + " with error code : " + result);
+ default:
+ return new AndroidRuntimeException("Start activity failed with error code : "
+ + result + " when starting " + intent);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1364c72e6275..3ccb06ccef15 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -25,6 +25,13 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
@@ -32,13 +39,6 @@ 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.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
import android.Manifest;
import android.annotation.NonNull;
@@ -57,6 +57,7 @@ import android.content.res.Configuration;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -75,6 +76,7 @@ import com.android.server.wm.ActivityTaskManagerService.HotPath;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -219,6 +221,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
/** Whether our process is currently running a {@link IRemoteAnimationRunner} */
private boolean mRunningRemoteAnimation;
+ /** List of "chained" processes that are running remote animations for this process */
+ private final ArrayList<WeakReference<WindowProcessController>> mRemoteAnimationDelegates =
+ new ArrayList<>();
+
// The bits used for mActivityStateFlags.
private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17;
@@ -258,7 +264,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
onConfigurationChanged(atm.getGlobalConfiguration());
- mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mName);
+ mAtm.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, mInfo.packageName);
}
public void setPid(int pid) {
@@ -506,19 +512,19 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
*/
@HotPath(caller = HotPath.START_SERVICE)
public boolean areBackgroundFgsStartsAllowed() {
- return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(),
+ return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(),
true /* isCheckingForFgsStart */);
}
- boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed) {
- return areBackgroundActivityStartsAllowed(appSwitchAllowed,
+ boolean areBackgroundActivityStartsAllowed(int appSwitchState) {
+ return areBackgroundActivityStartsAllowed(appSwitchState,
false /* isCheckingForFgsStart */);
}
- private boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed,
+ private boolean areBackgroundActivityStartsAllowed(int appSwitchState,
boolean isCheckingForFgsStart) {
return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName,
- appSwitchAllowed, isCheckingForFgsStart, hasActivityInVisibleTask(),
+ appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(),
mInstrumentingWithBackgroundActivityStartPrivileges,
mAtm.getLastStopAppSwitchesTime(),
mLastActivityLaunchTime, mLastActivityFinishTime);
@@ -725,20 +731,23 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
canUpdate = true;
}
- // Compare the z-order of ActivityStacks if both activities landed on same display.
- if (display == topDisplay
- && mPreQTopResumedActivity.getRootTask().compareTo(
- activity.getRootTask()) <= 0) {
- canUpdate = true;
+ // Update the topmost activity if the activity has higher z-order than the current
+ // top-resumed activity.
+ if (!canUpdate) {
+ final ActivityRecord ar = topDisplay.getActivity(r -> r == activity,
+ true /* traverseTopToBottom */, mPreQTopResumedActivity);
+ if (ar != null && ar != mPreQTopResumedActivity) {
+ canUpdate = true;
+ }
}
if (canUpdate) {
// Make sure the previous top activity in the process no longer be resumed.
if (mPreQTopResumedActivity != null && mPreQTopResumedActivity.isState(RESUMED)) {
- final Task task = mPreQTopResumedActivity.getTask();
- if (task != null) {
- boolean userLeaving = task.shouldBeVisible(null);
- task.startPausingLocked(userLeaving, false /* uiSleeping */,
+ final TaskFragment taskFrag = mPreQTopResumedActivity.getTaskFragment();
+ if (taskFrag != null) {
+ boolean userLeaving = taskFrag.shouldBeVisible(null);
+ taskFrag.startPausing(userLeaving, false /* uiSleeping */,
activity, "top-resumed-changed");
}
}
@@ -809,10 +818,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return false;
}
- void updateNightModeForAllActivities(int nightMode) {
+ // TODO(b/199277065): Re-assess how app-specific locales are applied based on UXR
+ // TODO(b/199277729): Consider whether we need to add special casing for edge cases like
+ // activity-embeddings etc.
+ void updateAppSpecificSettingsForAllActivities(Integer nightMode, LocaleList localesOverride) {
for (int i = mActivities.size() - 1; i >= 0; --i) {
final ActivityRecord r = mActivities.get(i);
- if (r.setOverrideNightMode(nightMode) && r.mVisibleRequested) {
+ if (r.applyAppSpecificConfig(nightMode, localesOverride) && r.mVisibleRequested) {
r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
}
}
@@ -940,7 +952,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
final int displayId = r.getDisplayId();
final Context c = root.getDisplayUiContext(displayId);
- if (r.mVisibleRequested && !displayContexts.contains(c)) {
+ if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
displayContexts.add(c);
}
}
@@ -991,7 +1003,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// Since there could be more than one activities in a process record, we don't need to
// compute the OomAdj with each of them, just need to find out the activity with the
// "best" state, the order would be visible, pausing, stopping...
- Task.ActivityState bestInvisibleState = DESTROYED;
+ ActivityRecord.State bestInvisibleState = DESTROYED;
boolean allStoppingFinishing = true;
boolean visible = false;
int minTaskLayer = Integer.MAX_VALUE;
@@ -1098,7 +1110,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
void updateProcessInfo(boolean updateServiceConnectionActivities, boolean activityChange,
boolean updateOomAdj, boolean addPendingTopUid) {
if (addPendingTopUid) {
- mAtm.mAmInternal.addPendingTopUid(mUid, mPid);
+ addToPendingTop();
}
if (updateOomAdj) {
prepareOomAdjustment();
@@ -1109,6 +1121,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mAtm.mH.sendMessage(m);
}
+ /** Makes the process have top state before oom-adj is computed from a posted message. */
+ void addToPendingTop() {
+ mAtm.mAmInternal.addPendingTopUid(mUid, mPid);
+ }
+
void updateServiceConnectionActivities() {
// Posting on handler so WM lock isn't held when we call into AM.
mAtm.mH.sendMessage(PooledLambda.obtainMessage(
@@ -1215,12 +1232,12 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
hasVisibleActivities = true;
}
- final Task task = r.getTask();
- if (task != null) {
+ final TaskFragment taskFragment = r.getTaskFragment();
+ if (taskFragment != null) {
// There may be a pausing activity that hasn't shown any window and was requested
// to be hidden. But pausing is also a visible state, it should be regarded as
// visible, so the caller can know the next activity should be resumed.
- hasVisibleActivities |= task.handleAppDied(this);
+ hasVisibleActivities |= taskFragment.handleAppDied(this);
}
r.handleAppDied();
}
@@ -1547,7 +1564,9 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// activity as it could lead to incorrect display metrics. For ex, IME services
// expect their config to match the config of the display with the IME window
// showing.
+ // If the configuration has been overridden by previous activity, empty it.
mIsActivityConfigOverrideAllowed = false;
+ unregisterActivityConfigurationListener();
break;
default:
break;
@@ -1596,11 +1615,38 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
updateRunningRemoteOrRecentsAnimation();
}
+ /**
+ * Marks another process as a "delegate" animator. This means that process is doing some part
+ * of a remote animation on behalf of this process.
+ */
+ void addRemoteAnimationDelegate(WindowProcessController delegate) {
+ if (!isRunningRemoteTransition()) {
+ throw new IllegalStateException("Can't add a delegate to a process which isn't itself"
+ + " running a remote animation");
+ }
+ mRemoteAnimationDelegates.add(new WeakReference<>(delegate));
+ }
+
void updateRunningRemoteOrRecentsAnimation() {
+ if (!isRunningRemoteTransition()) {
+ // Clean-up any delegates
+ for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) {
+ final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get();
+ if (delegate == null) continue;
+ delegate.setRunningRemoteAnimation(false);
+ delegate.setRunningRecentsAnimation(false);
+ }
+ mRemoteAnimationDelegates.clear();
+ }
+
// Posting on handler so WM lock isn't held when we call into AM.
mAtm.mH.sendMessage(PooledLambda.obtainMessage(
WindowProcessListener::setRunningRemoteAnimation, mListener,
- mRunningRecentsAnimation || mRunningRemoteAnimation));
+ isRunningRemoteTransition()));
+ }
+
+ boolean isRunningRemoteTransition() {
+ return mRunningRecentsAnimation || mRunningRemoteAnimation;
}
/** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0af6a29fad10..a16d9c196b93 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -33,6 +33,7 @@ import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -50,7 +51,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -105,15 +105,17 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
+import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
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_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_RESIZE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -151,8 +153,6 @@ 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.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
@@ -191,6 +191,7 @@ import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
import android.content.Context;
@@ -200,6 +201,7 @@ import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.gui.TouchOcclusionMode;
import android.os.Binder;
import android.os.Build;
import android.os.Debug;
@@ -209,7 +211,6 @@ import android.os.PowerManager.WakeReason;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.TouchOcclusionMode;
import android.os.Trace;
import android.os.WorkSource;
import android.provider.Settings;
@@ -235,6 +236,7 @@ import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -270,7 +272,7 @@ import java.util.function.Predicate;
/** A window in the window manager. */
class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
- InsetsControlTarget {
+ InsetsControlTarget, InputTarget {
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
// The minimal size of a window within the usable area of the freeform root task.
@@ -305,6 +307,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@NonNull WindowToken mToken;
// The same object as mToken if this is an app window and null for non-app windows.
ActivityRecord mActivityRecord;
+ /** Non-null if this is a starting window. */
+ StartingData mStartingData;
// mAttrs.flags is tested in animation without being locked. If the bits tested are ever
// modified they will need to be locked.
@@ -367,6 +371,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private boolean mRedrawForSyncReported;
/**
+ * {@code true} when the client was still drawing for sync when the sync-set was finished or
+ * cancelled. This can happen if the window goes away during a sync. In this situation we need
+ * to make sure to still apply the postDrawTransaction when it finishes to prevent the client
+ * from getting stuck in a bad state.
+ */
+ boolean mClientWasDrawingForSync = false;
+
+ /**
* Special mode that is intended only for the rounded corner overlay: during rotation
* transition, we un-rotate the window token such that the window appears as it did before the
* rotation.
@@ -745,7 +757,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private boolean mIsDimming = false;
private @Nullable InsetsSourceProvider mControllableInsetProvider;
- private final InsetsState mRequestedInsetsState = new InsetsState();
+ private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
/**
* Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
@@ -868,18 +880,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
@Override
public boolean getRequestedVisibility(@InternalInsetsType int type) {
- return mRequestedInsetsState.getSourceOrDefaultVisibility(type);
+ return mRequestedVisibilities.getVisibility(type);
+ }
+
+ /**
+ * Returns all the requested visibilities.
+ *
+ * @return an {@link InsetsVisibilities} as the requested visibilities.
+ */
+ InsetsVisibilities getRequestedVisibilities() {
+ return mRequestedVisibilities;
}
/**
* @see #getRequestedVisibility(int)
*/
- void updateRequestedVisibility(InsetsState state) {
- for (int i = 0; i < InsetsState.SIZE; i++) {
- final InsetsSource source = state.peekSource(i);
- if (source == null) continue;
- mRequestedInsetsState.addSource(source);
- }
+ void setRequestedVisibilities(InsetsVisibilities visibilities) {
+ mRequestedVisibilities.set(visibilities);
}
/**
@@ -1157,7 +1174,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
return TouchOcclusionMode.USE_OPACITY;
}
- if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL)) {
+ if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL) || inTransition()) {
return TouchOcclusionMode.USE_OPACITY;
}
return TouchOcclusionMode.BLOCK_UNTRUSTED;
@@ -1254,8 +1271,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
frame.inset(left, top, right, bottom);
}
- void computeFrameAndUpdateSourceFrame() {
- computeFrame();
+ void computeFrameAndUpdateSourceFrame(DisplayFrames displayFrames) {
+ computeFrame(displayFrames);
// Update the source frame to provide insets to other windows during layout. If the
// simulated frames exist, then this is not computing a stable result so just skip.
if (mControllableInsetProvider != null && mSimulatedWindowFrames == null) {
@@ -1266,7 +1283,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/**
* Perform standard frame computation. The result can be obtained with getFrame() if so desired.
*/
- void computeFrame() {
+ void computeFrame(DisplayFrames displayFrames) {
if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
@@ -1379,7 +1396,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int fw = windowFrames.mFrame.width();
final int fh = windowFrames.mFrame.height();
- applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame);
+ applyGravityAndUpdateFrame(windowFrames, layoutContainingFrame, layoutDisplayFrame,
+ displayFrames);
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
@@ -1427,14 +1445,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- // TODO: Look into whether this override is still necessary.
@Override
public Rect getBounds() {
- if (mActivityRecord != null) {
- return mActivityRecord.getBounds();
- } else {
- return super.getBounds();
- }
+ // The window bounds are used for layout in screen coordinates. If the token has bounds for
+ // size compatibility mode, its configuration bounds are app based coordinates which should
+ // not be used for layout.
+ return mToken.hasSizeCompatBounds() ? mToken.getBounds() : super.getBounds();
}
/** Retrieves the current frame of the window that the application sees. */
@@ -1472,6 +1488,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mAttrs;
}
+ WindowManager.LayoutParams getLayoutingAttrs(int rotation) {
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ return mAttrs;
+ }
+ final WindowManager.LayoutParams[] paramsForRotation = mAttrs.paramsForRotation;
+ if (paramsForRotation == null || paramsForRotation.length != 4
+ || paramsForRotation[rotation] == null) {
+ return mAttrs;
+ }
+ return paramsForRotation[rotation];
+ }
+
/** Retrieves the flags used to disable system UI functions. */
int getDisableFlags() {
return mDisableFlags;
@@ -1698,7 +1726,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return state;
}
- int getDisplayId() {
+ @Override
+ public int getDisplayId() {
final DisplayContent displayContent = getDisplayContent();
if (displayContent == null) {
return Display.INVALID_DISPLAY;
@@ -1706,10 +1735,29 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return displayContent.getDisplayId();
}
+ @Override
+ public WindowState getWindowState() {
+ return this;
+ }
+
+ @Override
+ public IWindow getIWindow() {
+ return mClient;
+ }
+
+ @Override
+ public int getPid() {
+ return mSession.mPid;
+ }
+
Task getTask() {
return mActivityRecord != null ? mActivityRecord.getTask() : null;
}
+ @Nullable TaskFragment getTaskFragment() {
+ return mActivityRecord != null ? mActivityRecord.getTaskFragment() : null;
+ }
+
@Nullable Task getRootTask() {
final Task task = getTask();
if (task != null) {
@@ -1837,9 +1885,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return super.hasContentToDisplay();
}
- @Override
- boolean isVisible() {
- return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()
+ private boolean isVisibleByPolicyOrInsets() {
+ return isVisibleByPolicy()
// If we don't have a provider, this window isn't used as a window generating
// insets, so nobody can hide it over the inset APIs.
&& (mControllableInsetProvider == null
@@ -1847,11 +1894,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
@Override
+ boolean isVisible() {
+ return wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicyOrInsets();
+ }
+
+ @Override
boolean isVisibleRequested() {
- if (shouldCheckTokenVisibleRequested()) {
- return isVisible() && mToken.isVisibleRequested();
+ final boolean localVisibleRequested =
+ wouldBeVisibleRequestedIfPolicyIgnored() && isVisibleByPolicyOrInsets();
+ if (localVisibleRequested && shouldCheckTokenVisibleRequested()) {
+ return mToken.isVisibleRequested();
}
- return isVisible();
+ return localVisibleRequested;
}
/**
@@ -1898,6 +1952,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return !isWallpaper || mToken.isVisible();
}
+ private boolean wouldBeVisibleRequestedIfPolicyIgnored() {
+ final WindowState parent = getParentWindow();
+ final boolean isParentHiddenRequested = parent != null && !parent.isVisibleRequested();
+ if (isParentHiddenRequested || mAnimatingExit || mDestroying) {
+ return false;
+ }
+ final boolean isWallpaper = mToken.asWallpaperToken() != null;
+ return !isWallpaper || mToken.isVisibleRequested();
+ }
+
/**
* Is this window visible, ignoring its app token? It is not visible if there is no surface,
* or we are in the process of running an exit animation that will remove the surface.
@@ -1928,10 +1992,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
- * Same as isVisible(), but we also count it as visible between the
- * call to IWindowSession.add() and the first relayout().
+ * Is this window capable of being visible (policy and content), in a visible part of the
+ * hierarchy, and, if an activity window, the activity is visible-requested. Note, this means
+ * if the activity is going-away, this will be {@code false} even when the window is visible.
+ *
+ * The 'adding' part refers to the period of time between IWindowSession.add() and the first
+ * relayout() -- which, for activities, is the same as visibleRequested.
+ *
+ * TODO(b/206005136): This is very similar to isVisibleRequested(). Investigate merging them.
*/
- boolean isVisibleOrAdding() {
+ boolean isVisibleRequestedOrAdding() {
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
@@ -2167,7 +2237,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWmService.mAccessibilityController;
final int winTransit = TRANSIT_EXIT;
mWinAnimator.applyAnimationLocked(winTransit, false /* isEntrance */);
- if (accessibilityController != null) {
+ if (accessibilityController.hasCallbacks()) {
accessibilityController.onWindowTransition(this, winTransit);
}
}
@@ -2188,7 +2258,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
if (isVisibleNow() && animateExit) {
mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onWindowTransition(this, TRANSIT_EXIT);
}
changed = true;
@@ -2238,7 +2308,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
startMoveAnimation(left, top);
}
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
}
updateLocationInParentDisplayIfNeeded();
@@ -2276,7 +2346,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
&& (mWindowFrames.mRelFrame.top != mWindowFrames.mLastRelFrame.top
|| mWindowFrames.mRelFrame.left != mWindowFrames.mLastRelFrame.left)
&& (!mIsChildWindow || !getParentWindow().hasMoved())
- && !mWmService.mAtmService.getTransitionController().isCollecting();
+ && !mTransitionController.isCollecting();
}
boolean isObscuringDisplay() {
@@ -2362,6 +2432,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
void removeImmediately() {
+ if (!mRemoved) {
+ // Destroy surface before super call. The general pattern is that the children need
+ // to be removed before the parent (so that the sync-engine tracking works). Since
+ // WindowStateAnimator is a "virtual" child, we have to do it manually here.
+ mWinAnimator.destroySurfaceLocked(getSyncTransaction());
+ }
super.removeImmediately();
if (mRemoved) {
@@ -2403,8 +2479,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
disposeInputChannel();
- mWinAnimator.destroySurfaceLocked(mTmpTransaction);
- mTmpTransaction.apply();
mSession.windowRemovedLocked();
try {
mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
@@ -2520,13 +2594,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
setDisplayLayoutNeeded();
mWmService.requestTraversal();
}
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onWindowTransition(this, transit);
}
}
final boolean isAnimating = mAnimatingExit
- || isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)
- && (mActivityRecord == null || !mActivityRecord.isWaitingForTransitionStart());
+ || isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES);
final boolean lastWindowIsStartingWindow = startingWindow && mActivityRecord != null
&& mActivityRecord.isLastWindow(this);
// We delay the removal of a window if it has a showing surface that can be used to run
@@ -2657,9 +2730,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ // Don't allow transient-launch activities to take IME.
+ if (rootTask != null && mActivityRecord != null
+ && mTransitionController.isTransientLaunch(mActivityRecord)) {
+ return false;
+ }
+
if (DEBUG_INPUT_METHOD) {
- Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());
- if (!isVisibleOrAdding()) {
+ Slog.i(TAG_WM, "isVisibleRequestedOrAdding " + this + ": "
+ + isVisibleRequestedOrAdding());
+ if (!isVisibleRequestedOrAdding()) {
Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
@@ -2673,7 +2753,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
}
- return isVisibleOrAdding();
+ return isVisibleRequestedOrAdding();
}
private final class DeadWindowEventReceiver extends InputEventReceiver {
@@ -2801,10 +2881,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- int getSurfaceTouchableRegion(Region region, int flags) {
- final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
+ void getSurfaceTouchableRegion(Region region, WindowManager.LayoutParams attrs) {
+ final boolean modal = attrs.isModal();
if (modal) {
- flags |= FLAG_NOT_TOUCH_MODAL;
if (mActivityRecord != null) {
// Limit the outer touch to the activity root task region.
updateRegionForModalActivityWindow(region);
@@ -2836,8 +2915,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mInvGlobalScale != 1.f) {
region.scale(mInvGlobalScale);
}
-
- return flags;
}
/**
@@ -2880,9 +2957,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// means we need to intercept touches outside of that window. The dim layer
// user associated with the window (task or root task) will give us the good
// bounds, as they would be used to display the dim layer.
- final Task task = getTask();
- if (task != null) {
- task.getDimBounds(mTmpRect);
+ final TaskFragment taskFragment = getTaskFragment();
+ if (taskFragment != null) {
+ final Task task = taskFragment.asTask();
+ if (task != null) {
+ task.getDimBounds(mTmpRect);
+ } else {
+ mTmpRect.set(taskFragment.getBounds());
+ }
} else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
@@ -3094,7 +3176,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
public String canReceiveKeysReason(boolean fromUserTouch) {
return "fromTouch= " + fromUserTouch
- + " isVisibleOrAdding=" + isVisibleOrAdding()
+ + " isVisibleRequestedOrAdding=" + isVisibleRequestedOrAdding()
+ " mViewVisibility=" + mViewVisibility
+ " mRemoveOnExit=" + mRemoveOnExit
+ " flags=" + mAttrs.flags
@@ -3106,7 +3188,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
public boolean canReceiveKeys(boolean fromUserTouch) {
- final boolean canReceiveKeys = isVisibleOrAdding()
+ final boolean canReceiveKeys = isVisibleRequestedOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
@@ -3372,7 +3454,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
public void pokeDrawLockLw(long timeout) {
- if (isVisibleOrAdding()) {
+ if (isVisibleRequestedOrAdding()) {
if (mDrawLock == null) {
// We want the tag name to be somewhat stable so that it is easier to correlate
// in wake lock statistics. So in particular, we don't want to include the
@@ -3532,10 +3614,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
// Exclude toast because legacy apps may show toast window by themselves, so the misused
// apps won't always be considered as foreground state.
- if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST) {
+ // Exclude private presentations as they can only be shown on private virtual displays and
+ // shouldn't be the cause of an app be considered foreground.
+ if (mAttrs.type >= FIRST_SYSTEM_WINDOW && mAttrs.type != TYPE_TOAST
+ && mAttrs.type != TYPE_PRIVATE_PRESENTATION) {
mWmService.mAtmService.mActiveUids.onNonAppSurfaceVisibilityChanged(mOwnerUid, shown);
}
- if (mIsImWindow && mWmService.mAccessibilityController != null) {
+ if (mIsImWindow && mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onImeSurfaceShownChanged(this, shown);
}
}
@@ -3680,10 +3765,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* {@link WindowManager.LayoutParams#FLAG_NOT_TOUCH_MODAL touch modality.}
*/
void getEffectiveTouchableRegion(Region outRegion) {
- final boolean modal = (mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
final DisplayContent dc = getDisplayContent();
- if (modal && dc != null) {
+ if (mAttrs.isModal() && dc != null) {
outRegion.set(dc.getBounds());
cropRegionToRootTaskBoundsIfNeeded(outRegion);
subtractTouchExcludeRegionIfNeeded(outRegion);
@@ -3865,7 +3949,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final boolean forceRelayout = syncRedraw || reportOrientation || isDragResizeChanged();
final DisplayContent displayContent = getDisplayContent();
final boolean alwaysConsumeSystemBars =
- displayContent.getDisplayPolicy().areSystemBarsForcedShownLw(this);
+ displayContent.getDisplayPolicy().areSystemBarsForcedShownLw();
final int displayId = displayContent.getDisplayId();
markRedrawForSyncReported();
@@ -3879,7 +3963,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
"Requested redraw for orientation change: %s", this);
}
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId);
}
updateLocationInParentDisplayIfNeeded();
@@ -3931,7 +4015,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* Called when the insets state changed.
*/
void notifyInsetsChanged() {
- ProtoLog.d(WM_DEBUG_IME, "notifyInsetsChanged for %s ", this);
+ ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this);
try {
mClient.insetsChanged(getCompatInsetsState(),
hasMoved(),
@@ -3943,7 +4027,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
public void notifyInsetsControlChanged() {
- ProtoLog.d(WM_DEBUG_IME, "notifyInsetsControlChanged for %s ", this);
+ ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsControlChanged for %s ", this);
if (mAppDied || mRemoved) {
return;
}
@@ -4364,9 +4448,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "mEmbeddedDisplayContents=" + mEmbeddedDisplayContents);
}
if (dumpAll) {
- final String visibilityString = mRequestedInsetsState.toSourceVisibilityString();
+ final String visibilityString = mRequestedVisibilities.toString();
if (!visibilityString.isEmpty()) {
- pw.println(prefix + "Requested visibility: " + visibilityString);
+ pw.println(prefix + "Requested visibilities: " + visibilityString);
}
}
}
@@ -4399,12 +4483,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
private void applyGravityAndUpdateFrame(WindowFrames windowFrames, Rect containingFrame,
- Rect displayFrame) {
+ Rect displayFrame, DisplayFrames displayFrames) {
final int pw = containingFrame.width();
final int ph = containingFrame.height();
final Task task = getTask();
final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();
- final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
+ final WindowManager.LayoutParams attrs = getLayoutingAttrs(displayFrames.mRotation);
+ final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// We need to fit it to the display if either
// a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
@@ -4414,49 +4499,54 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// screen, but SurfaceViews want to be always at a specific location so we don't fit it to
// the display.
final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
- || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
+ || ((attrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
final boolean hasCompatScale = hasCompatScale();
- if ((mAttrs.flags & FLAG_SCALED) != 0) {
- if (mAttrs.width < 0) {
+ if ((attrs.flags & FLAG_SCALED) != 0 || mAttrs != attrs) {
+ // For the window with different layout attrs for different rotations, we need to avoid
+ // using requested size. Otherwise, when finishing a simulated rotation, the information
+ // coming from WindowManagerServices to the ViewRootImpl may not contain the correct
+ // value for the new rotation, and there will be a quick flash of wrong layout when the
+ // simulated activity faded out.
+ if (attrs.width < 0) {
w = pw;
} else if (hasCompatScale) {
- w = (int)(mAttrs.width * mGlobalScale + .5f);
+ w = (int) (attrs.width * mGlobalScale + .5f);
} else {
- w = mAttrs.width;
+ w = attrs.width;
}
- if (mAttrs.height < 0) {
+ if (attrs.height < 0) {
h = ph;
} else if (hasCompatScale) {
- h = (int)(mAttrs.height * mGlobalScale + .5f);
+ h = (int) (attrs.height * mGlobalScale + .5f);
} else {
- h = mAttrs.height;
+ h = attrs.height;
}
} else {
- if (mAttrs.width == MATCH_PARENT) {
+ if (attrs.width == MATCH_PARENT) {
w = pw;
} else if (hasCompatScale) {
- w = (int)(mRequestedWidth * mGlobalScale + .5f);
+ w = (int) (mRequestedWidth * mGlobalScale + .5f);
} else {
w = mRequestedWidth;
}
- if (mAttrs.height == MATCH_PARENT) {
+ if (attrs.height == MATCH_PARENT) {
h = ph;
} else if (hasCompatScale) {
- h = (int)(mRequestedHeight * mGlobalScale + .5f);
+ h = (int) (mRequestedHeight * mGlobalScale + .5f);
} else {
h = mRequestedHeight;
}
}
if (hasCompatScale) {
- x = mAttrs.x * mGlobalScale;
- y = mAttrs.y * mGlobalScale;
+ x = attrs.x * mGlobalScale;
+ y = attrs.y * mGlobalScale;
} else {
- x = mAttrs.x;
- y = mAttrs.y;
+ x = attrs.x;
+ y = attrs.y;
}
if (inNonFullscreenContainer && !layoutInParentFrame()) {
@@ -4483,13 +4573,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
// Set mFrame
- Gravity.apply(mAttrs.gravity, w, h, containingFrame,
- (int) (x + mAttrs.horizontalMargin * pw),
- (int) (y + mAttrs.verticalMargin * ph), windowFrames.mFrame);
-
+ Gravity.apply(attrs.gravity, w, h, containingFrame,
+ (int) (x + attrs.horizontalMargin * pw),
+ (int) (y + attrs.verticalMargin * ph), windowFrames.mFrame);
// Now make sure the window fits in the overall display frame.
if (fitToDisplay) {
- Gravity.applyDisplay(mAttrs.gravity, displayFrame, windowFrames.mFrame);
+ Gravity.applyDisplay(attrs.gravity, displayFrame, windowFrames.mFrame);
}
// We need to make sure we update the CompatFrame as it is used for
@@ -4685,7 +4774,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final int drawState = mWinAnimator.mDrawState;
if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
if (mAttrs.type != TYPE_APPLICATION_STARTING) {
- mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);
+ mActivityRecord.onFirstWindowDrawn(this);
} else {
mActivityRecord.onStartingWindowDrawn();
}
@@ -4773,6 +4862,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
windowInfo.focused = isFocused();
Task task = getTask();
windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode();
+ windowInfo.taskId = task == null ? ActivityTaskManager.INVALID_TASK_ID : task.mTaskId;
windowInfo.hasFlagWatchOutsideTouch =
(mAttrs.flags & WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH) != 0;
@@ -4882,20 +4972,27 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback,
boolean traverseTopToBottom) {
- // If this window is the current IME target, so we need to process the IME windows
- // directly above it. The exception is if we are in split screen
- // in which case we process the IME at the DisplayContent level to
+ // No need to apply to IME window if the window is not the current IME layering target.
+ if (!isImeLayeringTarget()) {
+ return false;
+ }
+ // If we are in split screen which case we process the IME at the DisplayContent level to
// ensure it is above the docked divider.
- // (i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
- // window will be ignored to traverse when the IME target is still in split-screen mode).
- if (isImeLayeringTarget()
- && (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
- || getTask() == null)) {
- if (mDisplayContent.forAllImeWindows(callback, traverseTopToBottom)) {
- return true;
- }
+ // i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME
+ // window will be ignored to traverse when the IME target is still in split-screen mode.
+ if (mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()
+ && getTask() != null) {
+ return false;
}
- return false;
+ // Note that we don't process IME window if the IME input target is not on the screen.
+ // In case some unexpected IME visibility cases happen like starting the remote
+ // animation on the keyguard but seeing the IME window that originally on the app
+ // which behinds the keyguard.
+ final WindowState imeInputTarget = getImeInputTarget();
+ if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) {
+ return false;
+ }
+ return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
}
private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
@@ -4970,6 +5067,48 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return false;
}
+ private boolean shouldFinishAnimatingExit() {
+ // Exit animation might be applied soon.
+ if (inTransition()) {
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isTransition: %s",
+ this);
+ return false;
+ }
+ if (!mDisplayContent.okToAnimate()) {
+ return true;
+ }
+ // Exit animation is running.
+ if (isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)) {
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isAnimating: %s",
+ this);
+ return false;
+ }
+ // If the wallpaper is currently behind this app window, we need to change both of
+ // them inside of a transaction to avoid artifacts.
+ if (mDisplayContent.mWallpaperController.isWallpaperTarget(this)) {
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+ "shouldWaitAnimatingExit: isWallpaperTarget: %s", this);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * If this is window is stuck in the animatingExit status, resume clean up procedure blocked
+ * by the exit animation.
+ */
+ void cleanupAnimatingExitWindow() {
+ // TODO(b/205335975): WindowManagerService#tryStartExitingAnimation starts an exit animation
+ // and set #mAnimationExit. After the exit animation finishes, #onExitAnimationDone shall
+ // be called, but there seems to be a case that #onExitAnimationDone is not triggered, so
+ // a windows stuck in the animatingExit status.
+ if (mAnimatingExit && shouldFinishAnimatingExit()) {
+ ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Clear window stuck on animatingExit status: %s",
+ this);
+ onExitAnimationDone();
+ }
+ }
+
void onExitAnimationDone() {
if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
+ ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
@@ -4999,7 +5138,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (isAnimating()) {
return;
}
- if (mWmService.mAccessibilityController != null) {
+ if (mWmService.mAccessibilityController.hasCallbacks()) {
mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(getDisplayId());
}
@@ -5416,6 +5555,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mWillReplaceWindow;
}
+ private boolean isStartingWindowAssociatedToTask() {
+ return mStartingData != null && mStartingData.mAssociatedTask != null;
+ }
+
private void applyDims() {
if (!mAnimatingExit && mAppDied) {
mIsDimming = true;
@@ -5565,7 +5708,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
outPoint.offset(-parent.mWindowFrames.mFrame.left + mTmpPoint.x,
-parent.mWindowFrames.mFrame.top + mTmpPoint.y);
} else if (parentWindowContainer != null) {
- final Rect parentBounds = parentWindowContainer.getBounds();
+ final Rect parentBounds = isStartingWindowAssociatedToTask()
+ ? mStartingData.mAssociatedTask.getBounds()
+ : parentWindowContainer.getBounds();
outPoint.offset(-parentBounds.left, -parentBounds.top);
}
@@ -5602,9 +5747,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
boolean needsRelativeLayeringToIme() {
- // We only use the relative layering mode in split screen, as part of elevating the IME
- // and windows above it's target above the docked divider.
- if (!inSplitScreenWindowingMode()) {
+ // We use the relative layering when IME isn't attached to the app. Such as part of
+ // elevating the IME and windows above it's target above the docked divider in
+ // split-screen, or make the popupMenu to be above the IME when the parent window is the
+ // IME layering target in bubble/freeform mode.
+ if (mDisplayContent.shouldImeAttachedToApp()) {
return false;
}
@@ -5648,6 +5795,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
void assignLayer(Transaction t, int layer) {
+ if (isStartingWindowAssociatedToTask()) {
+ // The starting window should cover the task.
+ t.setLayer(mSurfaceControl, Integer.MAX_VALUE);
+ return;
+ }
// See comment in assignRelativeLayerForImeTargetChild
if (needsRelativeLayeringToIme()) {
getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
@@ -5660,6 +5812,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mIsDimming;
}
+ @Override
+ protected void reparentSurfaceControl(Transaction t, SurfaceControl newParent) {
+ if (isStartingWindowAssociatedToTask()) {
+ // Its surface is already put in task. Don't reparent when transferring starting window
+ // across activities.
+ return;
+ }
+ super.reparentSurfaceControl(t, newParent);
+ }
+
+ @Override
+ public SurfaceControl getAnimationLeashParent() {
+ if (isStartingWindowAssociatedToTask()) {
+ return mStartingData.mAssociatedTask.mSurfaceControl;
+ }
+ return super.getAnimationLeashParent();
+ }
+
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side and simply inherit
// the default implementation here.
@@ -5899,9 +6069,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
outSurfaceInsets.set(getAttrs().surfaceInsets);
final InsetsState state = getInsetsStateWithVisibilityOverride();
outInsets.set(state.calculateInsets(outFrame, systemBars(),
- false /* ignoreVisibility */));
+ false /* ignoreVisibility */).toRect());
outStableInsets.set(state.calculateInsets(outFrame, systemBars(),
- true /* ignoreVisibility */));
+ true /* ignoreVisibility */).toRect());
}
void setViewVisibility(int viewVisibility) {
@@ -5921,17 +6091,32 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
// perspective but at the WindowState level we need to wait for ourselves
- // to draw even if the children draw first our don't need to sync, so we start
+ // to draw even if the children draw first or don't need to sync, so we start
// in WAITING state rather than READY.
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
requestRedrawForSync();
-
- mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
- mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
- BLAST_TIMEOUT_DURATION);
return true;
}
+ @Override
+ boolean isSyncFinished() {
+ if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mViewVisibility == View.GONE
+ && !isVisibleRequested()) {
+ // Don't wait for GONE windows. However, we don't alter the state in case the window
+ // becomes un-gone while the syncset is still active.
+ return true;
+ }
+ return super.isSyncFinished();
+ }
+
+ @Override
+ void finishSync(Transaction outMergedTransaction, boolean cancel) {
+ if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
+ mClientWasDrawingForSync = true;
+ }
+ super.finishSync(outMergedTransaction, cancel);
+ }
+
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
if (mOrientationChangeRedrawRequestTime > 0) {
final long duration =
@@ -5947,15 +6132,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
executeDrawHandlers(postDrawTransaction);
+
+ final boolean applyPostDrawNow = mClientWasDrawingForSync && postDrawTransaction != null;
+ mClientWasDrawingForSync = false;
if (!onSyncFinishedDrawing()) {
- return mWinAnimator.finishDrawingLocked(postDrawTransaction);
+ return mWinAnimator.finishDrawingLocked(postDrawTransaction, applyPostDrawNow);
+ }
+
+ if (mActivityRecord != null
+ && mTransitionController.isShellTransitionsEnabled()
+ && mAttrs.type == TYPE_APPLICATION_STARTING) {
+ mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
+ .notifyStartingWindowDrawn(mActivityRecord);
}
if (postDrawTransaction != null) {
mSyncTransaction.merge(postDrawTransaction);
}
- mWinAnimator.finishDrawingLocked(null);
+ mWinAnimator.finishDrawingLocked(null, false /* forceApplyNow */);
// We always want to force a traversal after a finish draw for blast sync.
return true;
}
@@ -5993,8 +6188,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
boolean hasWallpaper() {
- return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
- || (mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox());
+ return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 || hasWallpaperForLetterboxBackground();
+ }
+
+ boolean hasWallpaperForLetterboxBackground() {
+ return mActivityRecord != null && mActivityRecord.hasWallpaperBackgroudForLetterbox();
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 80941961cc5a..423b3a05565e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -41,7 +41,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
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.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowManagerService.logWithStack;
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
@@ -71,7 +70,6 @@ import java.io.PrintWriter;
**/
class WindowStateAnimator {
static final String TAG = TAG_WITH_CLASS_NAME ? "WindowStateAnimator" : TAG_WM;
- static final int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
static final int PRESERVED_SURFACE_LAYER = 1;
/**
@@ -82,16 +80,10 @@ class WindowStateAnimator {
static final int ROOT_TASK_CLIP_AFTER_ANIM = 0;
/**
- * Mode how the window gets clipped by the root task bounds: The clipping should be applied
- * before applying the animation transformation, i.e. the root task bounds move with the window.
- */
- static final int ROOT_TASK_CLIP_BEFORE_ANIM = 1;
-
- /**
* Mode how window gets clipped by the root task bounds during an animation: Don't clip the
* window by the root task bounds.
*/
- static final int ROOT_TASK_CLIP_NONE = 2;
+ static final int ROOT_TASK_CLIP_NONE = 1;
// Unchanging local convenience fields.
final WindowManagerService mService;
@@ -230,7 +222,8 @@ class WindowStateAnimator {
}
}
- boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
+ boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
+ boolean forceApplyNow) {
final boolean startingWindow =
mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
if (startingWindow) {
@@ -255,12 +248,12 @@ class WindowStateAnimator {
// If there is no surface, the last draw was for the previous surface. We don't want to
// wait until the new surface is shown and instead just apply the transaction right
// away.
- if (mLastHidden && mDrawState != NO_SURFACE) {
+ if (mLastHidden && mDrawState != NO_SURFACE && !forceApplyNow) {
mPostDrawTransaction.merge(postDrawTransaction);
- layoutNeeded = true;
} else {
- postDrawTransaction.apply();
+ mWin.getSyncTransaction().merge(postDrawTransaction);
}
+ layoutNeeded = true;
}
return layoutNeeded;
@@ -684,7 +677,7 @@ class WindowStateAnimator {
applyAnimationLocked(transit, true);
}
- if (mService.mAccessibilityController != null) {
+ if (mService.mAccessibilityController.hasCallbacks()) {
mService.mAccessibilityController.onWindowTransition(mWin, transit);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 3cbc67c004cd..ad351f099e1f 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,9 +16,11 @@
package com.android.server.wm;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -230,6 +232,11 @@ class WindowToken extends WindowContainer<WindowState> {
ProtoLog.w(WM_DEBUG_WINDOW_MOVEMENT,
"removeAllWindowsIfPossible: removing win=%s", win);
win.removeIfPossible();
+ if (i > mChildren.size()) {
+ // It's possible for removeIfPossible to delete siblings (for example if it is a
+ // starting window, it will perform operations on the ActivityRecord).
+ i = mChildren.size();
+ }
}
}
@@ -453,9 +460,24 @@ class WindowToken extends WindowContainer<WindowState> {
}
Rect getFixedRotationBarContentFrame(int windowType) {
- return isFixedRotationTransforming()
- ? mFixedRotationTransformState.mBarContentFrames.get(windowType)
- : null;
+ if (!isFixedRotationTransforming()) {
+ return null;
+ }
+ if (!INSETS_LAYOUT_GENERALIZATION) {
+ return mFixedRotationTransformState.mBarContentFrames.get(windowType);
+ }
+ final DisplayFrames displayFrames = mFixedRotationTransformState.mDisplayFrames;
+ final Rect tmpRect = new Rect();
+ if (windowType == TYPE_NAVIGATION_BAR) {
+ tmpRect.set(displayFrames.mInsetsState.getSource(InsetsState.ITYPE_NAVIGATION_BAR)
+ .getFrame());
+ }
+ if (windowType == TYPE_STATUS_BAR) {
+ tmpRect.set(displayFrames.mInsetsState.getSource(InsetsState.ITYPE_STATUS_BAR)
+ .getFrame());
+ }
+ tmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+ return tmpRect;
}
InsetsState getFixedRotationTransformInsetsState() {
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 0bb97f560a1c..6204824d70a9 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -54,7 +54,8 @@ class WindowTracing {
private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024;
private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024;
private static final int BUFFER_CAPACITY_ALL = 4096 * 1024;
- private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb";
+ static final String WINSCOPE_EXT = ".winscope";
+ private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
private static final String TAG = "WindowTracing";
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4190a91710fc..94bc22a05d7a 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -59,6 +59,9 @@ using android::base::unique_fd;
namespace android {
+static bool cancelRunningCompaction;
+static bool compactionInProgress;
+
// Legacy method for compacting processes, any new code should
// use compactProcess instead.
static inline void compactProcessProcfs(int pid, const std::string& compactionType) {
@@ -83,9 +86,18 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT
// Skip compaction if failed to open pidfd with any error
return -errno;
}
+ compactionInProgress = true;
+ cancelRunningCompaction = false;
int64_t totalBytesCompacted = 0;
for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) {
+ if (CC_UNLIKELY(cancelRunningCompaction)) {
+ // There could be a significant delay betweenwhen a compaction
+ // is requested and when it is handled during this time
+ // our OOM adjust could have improved.
+ cancelRunningCompaction = false;
+ break;
+ }
int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase));
for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) {
vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start;
@@ -95,11 +107,13 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT
auto bytesCompacted =
process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0);
if (CC_UNLIKELY(bytesCompacted == -1)) {
+ compactionInProgress = false;
return -errno;
}
totalBytesCompacted += bytesCompacted;
}
+ compactionInProgress = false;
return totalBytesCompacted;
}
@@ -228,6 +242,12 @@ static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, job
}
}
+static void com_android_server_am_CachedAppOptimizer_cancelCompaction(JNIEnv*, jobject) {
+ if (compactionInProgress) {
+ cancelRunningCompaction = true;
+ }
+}
+
static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid,
jint compactionFlags) {
compactProcessOrFallback(pid, compactionFlags);
@@ -279,6 +299,8 @@ static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIE
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
+ {"cancelCompaction", "()V",
+ (void*)com_android_server_am_CachedAppOptimizer_cancelCompaction},
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
{"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp
index d43cf3f59170..6a50d3834355 100644
--- a/services/core/xsd/Android.bp
+++ b/services/core/xsd/Android.bp
@@ -14,7 +14,6 @@ xsd_config {
package_name: "com.android.server.pm.permission.configfile",
}
-
xsd_config {
name: "platform-compat-config",
srcs: ["platform-compat/config/platform-compat-config.xsd"],
@@ -42,6 +41,7 @@ xsd_config {
srcs: ["display-layout-config/display-layout-config.xsd"],
api_dir: "display-layout-config/schema",
package_name: "com.android.server.display.config.layout",
+ boolean_getter: true,
}
xsd_config {
diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd
index 94a398f2cdb7..86f41769008d 100644
--- a/services/core/xsd/device-state-config/device-state-config.xsd
+++ b/services/core/xsd/device-state-config/device-state-config.xsd
@@ -40,10 +40,19 @@
<xs:element name="name" type="xs:string" minOccurs="0">
<xs:annotation name="nullable" />
</xs:element>
+ <xs:element name="flags" type="flags" />
<xs:element name="conditions" type="conditions" />
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="flags">
+ <xs:sequence>
+ <xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation name="nullable" />
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="conditions">
<xs:sequence>
<xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0">
diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt
index 08fccf8ad949..a98d4e569cd6 100644
--- a/services/core/xsd/device-state-config/schema/current.txt
+++ b/services/core/xsd/device-state-config/schema/current.txt
@@ -11,9 +11,11 @@ package com.android.server.policy.devicestate.config {
public class DeviceState {
ctor public DeviceState();
method public com.android.server.policy.devicestate.config.Conditions getConditions();
+ method public com.android.server.policy.devicestate.config.Flags getFlags();
method public java.math.BigInteger getIdentifier();
method @Nullable public String getName();
method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
+ method public void setFlags(com.android.server.policy.devicestate.config.Flags);
method public void setIdentifier(java.math.BigInteger);
method public void setName(@Nullable String);
}
@@ -23,6 +25,11 @@ package com.android.server.policy.devicestate.config {
method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
}
+ public class Flags {
+ ctor public Flags();
+ method @Nullable public java.util.List<java.lang.String> getFlag();
+ }
+
public class LidSwitchCondition {
ctor public LidSwitchCondition();
method public boolean getOpen();
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index c542c0d0c382..e14139a0860a 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -52,6 +52,6 @@
<xs:element name="address" type="xs:nonNegativeInteger"/>
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
- <xs:attribute name="isDefault" type="xs:boolean" use="optional" />
+ <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
</xs:complexType>
</xs:schema>
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 817188509f81..f3915754a1a4 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -4,11 +4,11 @@ package com.android.server.display.config.layout {
public class Display {
ctor public Display();
method public java.math.BigInteger getAddress();
- method public boolean getEnabled();
- method public boolean getIsDefault();
+ method public boolean isDefaultDisplay();
+ method public boolean isEnabled();
method public void setAddress(java.math.BigInteger);
+ method public void setDefaultDisplay(boolean);
method public void setEnabled(boolean);
- method public void setIsDefault(boolean);
}
public class Layout {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 8ea21ec74ad6..a3017990543f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -46,6 +46,12 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache {
@GuardedBy("mLock")
private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+ /** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}.
+ *
+ * <p>For users affiliated with the device, they inherit the policy from {@code DO} so
+ * it will map to the {@code DO}'s policy. Otherwise it will map to the admin of the requesting
+ * user.
+ */
@GuardedBy("mLock")
private final SparseBooleanArray mCanGrantSensorsPermissions = new SparseBooleanArray();
@@ -102,17 +108,16 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache {
}
@Override
- public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle) {
+ public boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userId) {
synchronized (mLock) {
- return mCanGrantSensorsPermissions.get(userHandle, false);
+ return mCanGrantSensorsPermissions.get(userId, false);
}
}
/** Sets ahmin control over permission grants for user. */
- public void setAdminCanGrantSensorsPermissions(@UserIdInt int userHandle,
- boolean canGrant) {
+ public void setAdminCanGrantSensorsPermissions(@UserIdInt int userId, boolean canGrant) {
synchronized (mLock) {
- mCanGrantSensorsPermissions.put(userHandle, canGrant);
+ mCanGrantSensorsPermissions.put(userId, canGrant);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 99e28d101496..774a485aac6b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -258,6 +258,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManager.UserRestrictionSource;
import android.os.storage.StorageManager;
import android.permission.AdminPermissionControlParams;
import android.permission.IPermissionManager;
@@ -286,6 +287,7 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
@@ -327,7 +329,6 @@ import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.Owners.OwnerDto;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.RestrictionsSet;
@@ -698,6 +699,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private DevicePolicyConstants mConstants;
+ /**
+ * User to be switched to on {@code logoutUser()}.
+ *
+ * <p>Only used on devices with headless system user mode
+ */
+ @GuardedBy("getLockObject()")
+ private @UserIdInt int mLogoutUserId = UserHandle.USER_NULL;
+
+ /**
+ * User the network logging notification was sent to.
+ */
+ // Guarded by mHandler
+ private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL;
+
private static final boolean ENABLE_LOCK_GUARD = true;
/**
@@ -1259,17 +1274,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
// Used by DevicePolicyManagerServiceShellCommand
- List<OwnerDto> listAllOwners() {
+ List<OwnerShellData> listAllOwners() {
Preconditions.checkCallAuthorization(
hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
return mInjector.binderWithCleanCallingIdentity(() -> {
- List<OwnerDto> owners = mOwners.listAllOwners();
+ SparseArray<DevicePolicyData> userData;
+
+ // Gets the owners of "full users" first (device owner and profile owners)
+ List<OwnerShellData> owners = mOwners.listAllOwners();
synchronized (getLockObject()) {
for (int i = 0; i < owners.size(); i++) {
- OwnerDto owner = owners.get(i);
+ OwnerShellData owner = owners.get(i);
owner.isAffiliated = isUserAffiliatedWithDeviceLocked(owner.userId);
}
+ userData = mUserData;
+ }
+
+ // Then the owners of profile users (managed profiles)
+ for (int i = 0; i < userData.size(); i++) {
+ DevicePolicyData policyData = mUserData.valueAt(i);
+ int userId = userData.keyAt(i);
+ int parentUserId = mUserManagerInternal.getProfileParentId(userId);
+ boolean isProfile = parentUserId != userId;
+ if (!isProfile) continue;
+ for (int j = 0; j < policyData.mAdminList.size(); j++) {
+ ActiveAdmin admin = policyData.mAdminList.get(j);
+ OwnerShellData owner = OwnerShellData.forManagedProfileOwner(userId,
+ parentUserId, admin.info.getComponent());
+ owners.add(owner);
+ }
}
+
return owners;
});
}
@@ -1962,13 +1997,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid));
ActiveAdmin admin = policy.mAdminMap.get(adminComponent);
- if (admin == null) {
+ // Throwing combined exception message for both the cases here, because from different
+ // security exceptions it could be deduced if particular package is admin package.
+ if (admin == null || admin.getUid() != callerUid) {
throw new SecurityException(String.format(
- "No active admin for %s", adminComponent));
- }
- if (admin.getUid() != callerUid) {
- throw new SecurityException(String.format(
- "Admin %s is not owned by uid %d", adminComponent, callerUid));
+ "Admin %s does not exist or is not owned by uid %d", adminComponent,
+ callerUid));
}
if (callerPackage != null) {
Preconditions.checkArgument(callerPackage.equals(adminComponent.getPackageName()));
@@ -3764,7 +3798,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS)
+ ? getCallerIdentity() : getCallerIdentity(adminReceiver);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_ACTIVE_ADMIN);
enforceUserUnlocked(userHandle);
@@ -3781,8 +3816,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
+ adminReceiver);
return;
}
- Preconditions.checkCallAuthorization(admin.getUid() == caller.getUid()
- || hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+
mInjector.binderWithCleanCallingIdentity(() ->
removeActiveAdminLocked(adminReceiver, userHandle));
}
@@ -3817,9 +3851,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
+ private boolean notSupportedOnAutomotive(String method) {
+ if (mIsAutomotive) {
+ Slogf.i(LOG_TAG, "%s is not supported on automotive builds", method);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void setPasswordQuality(ComponentName who, int quality, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordQuality")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -3961,6 +4003,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ // System caller can query policy for a particular admin.
+ Preconditions.checkCallAuthorization(
+ who == null || isCallingFromPackage(who.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
synchronized (getLockObject()) {
int mode = PASSWORD_QUALITY_UNSPECIFIED;
@@ -4054,7 +4100,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumLength(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumLength")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -4176,7 +4222,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -4326,7 +4372,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -4336,7 +4382,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumUpperCase(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumUpperCase")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -4369,6 +4415,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumLowerCase(ComponentName who, int length, boolean parent) {
+ if (notSupportedOnAutomotive("setPasswordMinimumLowerCase")) {
+ return;
+ }
Objects.requireNonNull(who, "ComponentName is null");
final int userId = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
@@ -4399,7 +4448,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumLetters(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumLetters")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -4431,7 +4480,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumNumeric(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumNumeric")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -4463,7 +4512,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumSymbols(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumSymbols")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -4495,7 +4544,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
@Override
public void setPasswordMinimumNonLetter(ComponentName who, int length, boolean parent) {
- if (!mHasFeature) {
+ if (!mHasFeature || notSupportedOnAutomotive("setPasswordMinimumNonLetter")) {
return;
}
Objects.requireNonNull(who, "ComponentName is null");
@@ -4536,7 +4585,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -4954,6 +5003,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ // System caller can query policy for a particular admin.
+ Preconditions.checkCallAuthorization(
+ who == null || isCallingFromPackage(who.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
synchronized (getLockObject()) {
ActiveAdmin admin = (who != null)
@@ -5265,6 +5318,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ // System caller can query policy for a particular admin.
+ Preconditions.checkCallAuthorization(
+ who == null || isCallingFromPackage(who.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
synchronized (getLockObject()) {
if (who != null) {
@@ -5342,7 +5399,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(who);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userId));
if (!mLockPatternUtils.hasSecureLockScreen()) {
@@ -7407,7 +7464,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
final CallerIdentity caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ hasFullCrossUsersPermission(caller, userHandle) && isSystemUid(caller));
synchronized (getLockObject()) {
DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
@@ -7685,6 +7743,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (!mHasFeature) {
return false;
}
+
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+
if (parent) {
Preconditions.checkCallAuthorization(
isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
@@ -8137,17 +8199,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
*/
@Override
public boolean getCameraDisabled(ComponentName who, int userHandle, boolean parent) {
- return getCameraDisabled(who, userHandle, /* mergeDeviceOwnerRestriction= */ true, parent);
- }
-
- private boolean getCameraDisabled(ComponentName who, int userHandle,
- boolean mergeDeviceOwnerRestriction, boolean parent) {
if (!mHasFeature) {
return false;
}
+
+ final CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+
if (parent) {
Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(getCallerIdentity().getUserId()));
+ isProfileOwnerOfOrganizationOwnedDevice(caller.getUserId()));
}
synchronized (getLockObject()) {
@@ -8156,17 +8217,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return (admin != null) && admin.disableCamera;
}
// First, see if DO has set it. If so, it's device-wide.
- if (mergeDeviceOwnerRestriction) {
- final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
- if (deviceOwner != null && deviceOwner.disableCamera) {
- return true;
- }
+ final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ if (deviceOwner != null && deviceOwner.disableCamera) {
+ return true;
}
final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
// Return the strictest policy across all participating admins.
List<ActiveAdmin> admins = getActiveAdminsForAffectedUserLocked(affectedUserId);
// Determine whether or not the device camera is disabled for any active admins.
- for (ActiveAdmin admin: admins) {
+ for (ActiveAdmin admin : admins) {
if (admin.disableCamera) {
return true;
}
@@ -8230,6 +8289,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ who == null || isCallingFromPackage(who.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
final long ident = mInjector.binderClearCallingIdentity();
try {
@@ -8337,7 +8399,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
- public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) {
+ public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId,
+ boolean setProfileOwnerOnCurrentUserIfNecessary) {
if (!mHasFeature) {
logMissingFeatureAction("Cannot set " + ComponentName.flattenToShortString(admin)
+ " as device owner for user " + userId);
@@ -8400,7 +8463,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Slogf.i(LOG_TAG, "Device owner set: " + admin + " on user " + userId);
- if (mInjector.userManagerIsHeadlessSystemUserMode()) {
+ if (setProfileOwnerOnCurrentUserIfNecessary
+ && mInjector.userManagerIsHeadlessSystemUserMode()) {
int currentForegroundUser = getCurrentForegroundUserId();
Slogf.i(LOG_TAG, "setDeviceOwner(): setting " + admin
+ " as profile owner on user " + currentForegroundUser);
@@ -9128,9 +9192,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
/**
- * Returns the ActiveAdmin associated wit the PO or DO on the given user.
- * @param userHandle
- * @return
+ * Returns the ActiveAdmin associated with the PO or DO on the given user.
*/
private @Nullable ActiveAdmin getDeviceOrProfileOwnerAdminLocked(int userHandle) {
ActiveAdmin admin = getProfileOwnerAdminLocked(userHandle);
@@ -9537,7 +9599,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private @UserIdInt int getCurrentForegroundUserId() {
try {
- return mInjector.getIActivityManager().getCurrentUser().id;
+ UserInfo currentUser = mInjector.getIActivityManager().getCurrentUser();
+ if (currentUser == null) {
+ // TODO(b/206107460): should not happen on production, but it's happening on unit
+ // tests that are not properly setting the expectation (because they don't need it)
+ Slogf.wtf(LOG_TAG, "getCurrentForegroundUserId(): mInjector.getIActivityManager()"
+ + ".getCurrentUser() returned null, please ignore when running unit tests");
+ return ActivityManager.getCurrentUser();
+ }
+ return currentUser.id;
} catch (RemoteException e) {
Slogf.wtf(LOG_TAG, "cannot get current user");
}
@@ -9654,6 +9724,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mStatLogger.dump(pw);
pw.println();
pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
+ pw.println("Logout user: " + getLogoutUserIdUnchecked());
pw.println();
if (mPendingUserCreatedCallbackTokens.isEmpty()) {
@@ -9670,10 +9741,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
mStateCache.dump(pw);
pw.println();
}
+ mHandler.post(() -> handleDump(pw));
dumpResources(pw);
}
}
+ // Dump state that is guarded by the handler
+ private void handleDump(IndentingPrintWriter pw) {
+ if (mNetworkLoggingNotificationUserId != UserHandle.USER_NULL) {
+ pw.println("mNetworkLoggingNotificationUserId: " + mNetworkLoggingNotificationUserId);
+ }
+ }
+
private void dumpImmutableState(IndentingPrintWriter pw) {
pw.println("Immutable state:");
pw.increaseIndent();
@@ -9888,7 +9967,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Objects.requireNonNull(agent, "agent null");
Preconditions.checkArgumentNonnegative(userHandle, "Invalid userId");
- final CallerIdentity caller = getCallerIdentity();
+ final CallerIdentity caller = getCallerIdentity(admin);
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
synchronized (getLockObject()) {
@@ -10613,19 +10692,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
final String adminPkg = admin.getPackageName();
- try {
- // Install the profile owner if not present.
- if (!mIPackageManager.isPackageAvailable(adminPkg, userId)) {
- mIPackageManager.installExistingPackageAsUser(adminPkg, userId,
- PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
- PackageManager.INSTALL_REASON_POLICY,
- /* allowlistedRestrictedPermissions= */ null);
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ try {
+ // Install the profile owner if not present.
+ if (!mIPackageManager.isPackageAvailable(adminPkg, userId)) {
+ mIPackageManager.installExistingPackageAsUser(adminPkg, userId,
+ PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS,
+ PackageManager.INSTALL_REASON_POLICY,
+ /* allowlistedRestrictedPermissions= */ null);
+ }
+ } catch (RemoteException e) {
+ // Does not happen, same process
+ Slogf.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d",
+ adminPkg, userId);
}
- } catch (RemoteException e) {
- // Does not happen, same process
- Slogf.wtf(LOG_TAG, e, "Failed to install admin package %s for user %d",
- adminPkg, userId);
- }
+ });
// Set admin.
setActiveAdmin(profileOwner, /* refreshing= */ true, userId);
@@ -10664,7 +10745,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- if (!mOwners.hasDeviceOwner() || !user.isFull() || user.isManagedProfile()) return;
+ if (!mOwners.hasDeviceOwner() || !user.isFull() || user.isManagedProfile()
+ || user.isGuest()) {
+ return;
+ }
if (mInjector.userManagerIsHeadlessSystemUserMode()) {
ComponentName admin = mOwners.getDeviceOwnerComponent();
@@ -10758,6 +10842,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
Preconditions.checkCallAuthorization(isDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER);
+ boolean switched = false;
+ // Save previous logout user id in case of failure
+ int logoutUserId = getLogoutUserIdUnchecked();
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
@@ -10765,17 +10852,72 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
if (userHandle != null) {
userId = userHandle.getIdentifier();
}
- return mInjector.getIActivityManager().switchUser(userId);
+ Slogf.i(LOG_TAG, "Switching to user %d (logout user is %d)", userId, logoutUserId);
+ setLogoutUserIdLocked(UserHandle.USER_CURRENT);
+ switched = mInjector.getIActivityManager().switchUser(userId);
+ if (!switched) {
+ Slogf.w(LOG_TAG, "Failed to switch to user %d", userId);
+ }
+ return switched;
} catch (RemoteException e) {
Slogf.e(LOG_TAG, "Couldn't switch user", e);
return false;
} finally {
mInjector.binderRestoreCallingIdentity(id);
+ if (!switched) {
+ setLogoutUserIdLocked(logoutUserId);
+ }
}
}
}
@Override
+ public int getLogoutUserId() {
+ Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()));
+
+ return getLogoutUserIdUnchecked();
+ }
+
+ private @UserIdInt int getLogoutUserIdUnchecked() {
+ if (!mInjector.userManagerIsHeadlessSystemUserMode()) {
+ // mLogoutUserId is USER_SYSTEM as well, but there's no need to acquire the lock
+ return UserHandle.USER_SYSTEM;
+ }
+ synchronized (getLockObject()) {
+ return mLogoutUserId;
+ }
+ }
+
+ @Override
+ public void clearLogoutUser() {
+ CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
+
+ Slogf.i(LOG_TAG, "Clearing logout user as requested by %s", caller);
+ clearLogoutUserUnchecked();
+ }
+
+ private void clearLogoutUserUnchecked() {
+ if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore
+
+ synchronized (getLockObject()) {
+ setLogoutUserIdLocked(UserHandle.USER_NULL);
+ }
+ }
+
+ @GuardedBy("getLockObject()")
+ private void setLogoutUserIdLocked(@UserIdInt int userId) {
+ if (!mInjector.userManagerIsHeadlessSystemUserMode()) return; // ignore
+
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = getCurrentForegroundUserId();
+ }
+
+ Slogf.d(LOG_TAG, "setLogoutUserId(): %d -> %d", mLogoutUserId, userId);
+ mLogoutUserId = userId;
+ }
+
+ @Override
public int startUserInBackground(ComponentName who, UserHandle userHandle) {
Objects.requireNonNull(who, "ComponentName is null");
Objects.requireNonNull(userHandle, "UserHandle is null");
@@ -10796,10 +10938,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return UserManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS;
}
+ Slogf.i(LOG_TAG, "Starting user %d in background", userId);
if (mInjector.getIActivityManager().startUserInBackground(userId)) {
- Slogf.i(LOG_TAG, "Started used %d in background", userId);
return UserManager.USER_OPERATION_SUCCESS;
} else {
+ Slogf.w(LOG_TAG, "failed to start user %d in background", userId);
return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
} catch (RemoteException e) {
@@ -10847,13 +10990,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return UserManager.USER_OPERATION_ERROR_MANAGED_PROFILE;
}
+ // TODO(b/204585343): remove the headless system user check?
+ if (mInjector.userManagerIsHeadlessSystemUserMode() && callingUserId != mInjector
+ .binderWithCleanCallingIdentity(() -> getCurrentForegroundUserId())) {
+ Slogf.d(LOG_TAG, "logoutUser(): user %d is in background, just stopping, not switching",
+ callingUserId);
+ return stopUserUnchecked(callingUserId);
+ }
+
+ int logoutUserId = getLogoutUserIdUnchecked();
+ if (logoutUserId == UserHandle.USER_NULL) {
+ // Could happen on devices using headless system user mode when called before calling
+ // switchUser() or startUserInBackground() first
+ Slogf.w(LOG_TAG, "logoutUser(): could not determine which user to switch to");
+ return UserManager.USER_OPERATION_ERROR_UNKNOWN;
+ }
final long id = mInjector.binderClearCallingIdentity();
try {
- if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) {
- Slogf.w(LOG_TAG, "Failed to switch to primary user");
- // This should never happen as target user is UserHandle.USER_SYSTEM
+ Slogf.i(LOG_TAG, "logoutUser(): switching to user %d", logoutUserId);
+ if (!mInjector.getIActivityManager().switchUser(logoutUserId)) {
+ Slogf.w(LOG_TAG, "Failed to switch to user %d", logoutUserId);
+ // This should never happen as target user is determined by getPreviousUserId()
return UserManager.USER_OPERATION_ERROR_UNKNOWN;
}
+ clearLogoutUserUnchecked();
} catch (RemoteException e) {
// Same process, should not happen.
return UserManager.USER_OPERATION_ERROR_UNKNOWN;
@@ -10864,7 +11024,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return stopUserUnchecked(callingUserId);
}
- private int stopUserUnchecked(int userId) {
+ private int stopUserUnchecked(@UserIdInt int userId) {
+ Slogf.i(LOG_TAG, "Stopping user %d", userId);
final long id = mInjector.binderClearCallingIdentity();
try {
switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
@@ -13083,14 +13244,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
try {
List<UserManager.EnforcingUser> sources = mUserManager
.getUserRestrictionSources(restriction, UserHandle.of(userId));
- if (sources == null || sources.isEmpty()) {
+ if (sources == null) {
// The restriction is not enforced.
return null;
- } else if (sources.size() > 1) {
+ }
+ int sizeBefore = sources.size();
+ if (sizeBefore > 1) {
+ Slogf.d(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): "
+ + "%d sources found, excluding those set by UserManager",
+ userId, restriction, sizeBefore);
+ sources = getDevicePolicySources(sources);
+ }
+ if (sources.isEmpty()) {
+ // The restriction is not enforced (or is just enforced by the system)
+ return null;
+ }
+
+ if (sources.size() > 1) {
// In this case, we'll show an admin support dialog that does not
// specify the admin.
// TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
// the admin for the calling user.
+ Slogf.w(LOG_TAG, "getEnforcingAdminAndUserDetailsInternal(%d, %s): multiple "
+ + "sources for restriction %s on user %d", restriction, userId);
result = new Bundle();
result.putInt(Intent.EXTRA_USER_ID, userId);
return result;
@@ -13136,6 +13312,32 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
/**
+ * Excludes restrictions imposed by UserManager.
+ */
+ private List<UserManager.EnforcingUser> getDevicePolicySources(
+ List<UserManager.EnforcingUser> sources) {
+ int sizeBefore = sources.size();
+ List<UserManager.EnforcingUser> realSources = new ArrayList<>(sizeBefore);
+ for (int i = 0; i < sizeBefore; i++) {
+ UserManager.EnforcingUser source = sources.get(i);
+ int type = source.getUserRestrictionSource();
+ if (type != UserManager.RESTRICTION_SOURCE_PROFILE_OWNER
+ && type != UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
+ // TODO(b/128928355): add unit test
+ Slogf.d(LOG_TAG, "excluding source of type %s at index %d",
+ userRestrictionSourceToString(type), i);
+ continue;
+ }
+ realSources.add(source);
+ }
+ return realSources;
+ }
+
+ private static String userRestrictionSourceToString(@UserRestrictionSource int source) {
+ return DebugUtils.flagsToString(UserManager.class, "RESTRICTION_", source);
+ }
+
+ /**
* @param restriction The restriction enforced by admin. It could be any user restriction or
* policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
* {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
@@ -14344,6 +14546,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
maybePauseDeviceWideLoggingLocked();
maybeResumeDeviceWideLoggingLocked();
maybeClearLockTaskPolicyLocked();
+ updateAdminCanGrantSensorsPermissionCache(callingUserId);
}
}
@@ -15077,11 +15280,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
if (active) {
if (shouldSendNotification) {
- mHandler.post(() -> sendNetworkLoggingNotification());
+ mHandler.post(() -> handleSendNetworkLoggingNotification());
}
} else {
- mHandler.post(() -> mInjector.getNotificationManager().cancel(
- SystemMessage.NOTE_NETWORK_LOGGING));
+ mHandler.post(() -> handleCancelNetworkLoggingNotification());
}
});
}
@@ -15272,10 +15474,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
return true;
}
- private void sendNetworkLoggingNotification() {
+ private void handleSendNetworkLoggingNotification() {
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG);
intent.setPackage(pm.getSystemUiServiceComponent().getPackageName());
+ mNetworkLoggingNotificationUserId = getCurrentForegroundUserId();
// Simple notification clicks are immutable
final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent,
PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
@@ -15290,7 +15493,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.setStyle(new Notification.BigTextStyle()
.bigText(mContext.getString(R.string.network_logging_notification_text)))
.build();
- mInjector.getNotificationManager().notify(SystemMessage.NOTE_NETWORK_LOGGING, notification);
+ Slogf.i(LOG_TAG, "Sending network logging notification to user %d",
+ mNetworkLoggingNotificationUserId);
+ mInjector.getNotificationManager().notifyAsUser(/* tag= */ null,
+ SystemMessage.NOTE_NETWORK_LOGGING, notification,
+ UserHandle.of(mNetworkLoggingNotificationUserId));
+ }
+
+ private void handleCancelNetworkLoggingNotification() {
+ if (mNetworkLoggingNotificationUserId == UserHandle.USER_NULL) {
+ // Happens when setNetworkLoggingActive(false) is called before called with true
+ Slogf.d(LOG_TAG, "Not cancelling network logging notification for USER_NULL");
+ return;
+ }
+
+ Slogf.i(LOG_TAG, "Cancelling network logging notification for user %d",
+ mNetworkLoggingNotificationUserId);
+ mInjector.getNotificationManager().cancelAsUser(/* tag= */ null,
+ SystemMessage.NOTE_NETWORK_LOGGING,
+ UserHandle.of(mNetworkLoggingNotificationUserId));
+ mNetworkLoggingNotificationUserId = UserHandle.USER_NULL;
}
/**
@@ -17007,6 +17229,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
@Override
+ public void clearOrganizationIdForUser(int userHandle) {
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
+ synchronized (getLockObject()) {
+ final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle);
+ owner.mOrganizationId = null;
+ owner.mEnrollmentSpecificId = null;
+ saveSettingsLocked(userHandle);
+ }
+ }
+
+ @Override
public UserHandle createAndProvisionManagedProfile(
@NonNull ManagedProfileProvisioningParams provisioningParams,
@NonNull String callerPackage) {
@@ -17453,7 +17688,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// TODO(b/178187130): Directly set DO and remove the check once silent provisioning is no
// longer used.
if (getDeviceOwnerComponent(/* callingUserOnly= */ true) == null) {
- return setDeviceOwner(adminComponent, name, userId);
+ return setDeviceOwner(adminComponent, name, userId,
+ /* setProfileOwnerOnCurrentUserIfNecessary= */ true);
}
return true;
}
@@ -17508,7 +17744,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
}
- private void setAdminCanGrantSensorsPermissionForUserUnchecked(int userId, boolean canGrant) {
+ private void setAdminCanGrantSensorsPermissionForUserUnchecked(@UserIdInt int userId,
+ boolean canGrant) {
+ Slogf.d(LOG_TAG, "setAdminCanGrantSensorsPermissionForUserUnchecked(%d, %b)",
+ userId, canGrant);
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
@@ -17522,10 +17761,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private void updateAdminCanGrantSensorsPermissionCache(int userId) {
+ private void updateAdminCanGrantSensorsPermissionCache(@UserIdInt int userId) {
synchronized (getLockObject()) {
- ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
- final boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+
+ ActiveAdmin owner;
+ // If the user is affiliated the device (either a DO itself, or an affiliated PO),
+ // use mAdminCanGrantSensorsPermissions from the DO
+ if (isUserAffiliatedWithDeviceLocked(userId)) {
+ owner = getDeviceOwnerAdminLocked();
+ } else {
+ owner = getDeviceOrProfileOwnerAdminLocked(userId);
+ }
+ boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
mPolicyCache.setAdminCanGrantSensorsPermissions(userId, canGrant);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index a2db6aaca3df..e1d720ca25c8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -22,8 +22,6 @@ import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.UserHandle;
-import com.android.server.devicepolicy.Owners.OwnerDto;
-
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
@@ -48,11 +46,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
private static final String USER_OPTION = "--user";
private static final String NAME_OPTION = "--name";
+ private static final String DO_ONLY_OPTION = "--device-owner-only";
private final DevicePolicyManagerService mService;
private int mUserId = UserHandle.USER_SYSTEM;
private String mName = "";
private ComponentName mComponent;
+ private boolean mSetDoOnly;
DevicePolicyManagerServiceShellCommand(DevicePolicyManagerService service) {
mService = Objects.requireNonNull(service);
@@ -132,8 +132,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
CMD_SET_ACTIVE_ADMIN, USER_OPTION);
pw.printf(" Sets the given component as active admin for an existing user.\n\n");
- pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s <NAME> ] "
- + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, NAME_OPTION);
+ pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s <NAME> ] [ %s ]"
+ + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, NAME_OPTION, DO_ONLY_OPTION);
pw.printf(" Sets the given component as active admin, and its package as device owner."
+ "\n\n");
pw.printf(" %s [ %s <USER_ID> | current ] [ %s <NAME> ] <COMPONENT>\n",
@@ -205,12 +205,12 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
private int runListOwners(PrintWriter pw) {
- List<OwnerDto> owners = mService.listAllOwners();
+ List<OwnerShellData> owners = mService.listAllOwners();
int size = printAndGetSize(pw, owners, "owner");
if (size == 0) return 0;
for (int i = 0; i < size; i++) {
- OwnerDto owner = owners.get(i);
+ OwnerShellData owner = owners.get(i);
pw.printf("User %2d: admin=%s", owner.userId, owner.admin.flattenToShortString());
if (owner.isDeviceOwner) {
pw.print(",DeviceOwner");
@@ -218,6 +218,9 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
if (owner.isProfileOwner) {
pw.print(",ProfileOwner");
}
+ if (owner.isManagedProfileOwner) {
+ pw.printf(",ManagedProfileOwner(parentUserId=%d)", owner.parentUserId);
+ }
if (owner.isAffiliated) {
pw.print(",Affiliated");
}
@@ -253,7 +256,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
try {
- if (!mService.setDeviceOwner(mComponent, mName, mUserId)) {
+ if (!mService.setDeviceOwner(mComponent, mName, mUserId,
+ /* setProfileOwnerOnCurrentUserIfNecessary= */ !mSetDoOnly)) {
throw new RuntimeException(
"Can't set package " + mComponent + " as device owner.");
}
@@ -350,6 +354,8 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
if (mUserId == UserHandle.USER_CURRENT) {
mUserId = ActivityManager.getCurrentUser();
}
+ } else if (DO_ONLY_OPTION.equals(opt)) {
+ mSetDoOnly = true;
} else if (canHaveName && NAME_OPTION.equals(opt)) {
mName = getNextArgRequired();
} else {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java
new file mode 100644
index 000000000000..b98c3dc2ac07
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnerShellData.java
@@ -0,0 +1,98 @@
+/*
+ * 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.devicepolicy;
+
+import static android.os.UserHandle.USER_NULL;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Data-transfer object used by {@link DevicePolicyManagerServiceShellCommand}.
+ */
+final class OwnerShellData {
+
+ public final @UserIdInt int userId;
+ public final @UserIdInt int parentUserId;
+ public final ComponentName admin;
+ public final boolean isDeviceOwner;
+ public final boolean isProfileOwner;
+ public final boolean isManagedProfileOwner;
+ public boolean isAffiliated;
+
+ // NOTE: class is too simple to require a Builder (not to mention isAffiliated is mutable)
+ private OwnerShellData(@UserIdInt int userId, @UserIdInt int parentUserId, ComponentName admin,
+ boolean isDeviceOwner, boolean isProfileOwner, boolean isManagedProfileOwner) {
+ Preconditions.checkArgument(userId != USER_NULL, "userId cannot be USER_NULL");
+ this.userId = userId;
+ this.parentUserId = parentUserId;
+ this.admin = Objects.requireNonNull(admin, "admin must not be null");
+ this.isDeviceOwner = isDeviceOwner;
+ this.isProfileOwner = isProfileOwner;
+ this.isManagedProfileOwner = isManagedProfileOwner;
+ if (isManagedProfileOwner) {
+ Preconditions.checkArgument(parentUserId != USER_NULL,
+ "parentUserId cannot be USER_NULL for managed profile owner");
+ Preconditions.checkArgument(parentUserId != userId,
+ "cannot be parent of itself (%d)", userId);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(getClass().getSimpleName())
+ .append("[userId=").append(userId)
+ .append(",admin=").append(admin.flattenToShortString());
+ if (isDeviceOwner) {
+ sb.append(",deviceOwner");
+ }
+ if (isProfileOwner) {
+ sb.append(",isProfileOwner");
+ }
+ if (isManagedProfileOwner) {
+ sb.append(",isManagedProfileOwner");
+ }
+ if (parentUserId != USER_NULL) {
+ sb.append(",parentUserId=").append(parentUserId);
+ }
+ if (isAffiliated) {
+ sb.append(",isAffiliated");
+ }
+ return sb.append(']').toString();
+ }
+
+ static OwnerShellData forDeviceOwner(@UserIdInt int userId, ComponentName admin) {
+ return new OwnerShellData(userId, /* parentUserId= */ USER_NULL, admin,
+ /* isDeviceOwner= */ true, /* isProfileOwner= */ false,
+ /* isManagedProfileOwner= */ false);
+ }
+
+ static OwnerShellData forUserProfileOwner(@UserIdInt int userId, ComponentName admin) {
+ return new OwnerShellData(userId, /* parentUserId= */ USER_NULL, admin,
+ /* isDeviceOwner= */ false, /* isProfileOwner= */ true,
+ /* isManagedProfileOwner= */ false);
+ }
+
+ static OwnerShellData forManagedProfileOwner(@UserIdInt int userId, @UserIdInt int parentUserId,
+ ComponentName admin) {
+ return new OwnerShellData(userId, parentUserId, admin, /* isDeviceOwner= */ false,
+ /* isProfileOwner= */ false, /* isManagedProfileOwner= */ true);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index fd09e3f9cfd0..3584728a2e62 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -19,7 +19,6 @@ package com.android.server.devicepolicy;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManagerInternal;
import android.app.admin.DevicePolicyManager.DeviceOwnerType;
@@ -476,17 +475,16 @@ class Owners {
}
}
- List<OwnerDto> listAllOwners() {
- List<OwnerDto> owners = new ArrayList<>();
+ List<OwnerShellData> listAllOwners() {
+ List<OwnerShellData> owners = new ArrayList<>();
synchronized (mLock) {
if (mDeviceOwner != null) {
- owners.add(new OwnerDto(mDeviceOwnerUserId, mDeviceOwner.admin,
- /* isDeviceOwner= */ true));
+ owners.add(OwnerShellData.forDeviceOwner(mDeviceOwnerUserId, mDeviceOwner.admin));
}
for (int i = 0; i < mProfileOwners.size(); i++) {
int userId = mProfileOwners.keyAt(i);
OwnerInfo info = mProfileOwners.valueAt(i);
- owners.add(new OwnerDto(userId, info.admin, /* isDeviceOwner= */ false));
+ owners.add(OwnerShellData.forUserProfileOwner(userId, info.admin));
}
}
return owners;
@@ -1236,24 +1234,6 @@ class Owners {
}
}
- /**
- * Data-transfer object used by {@link DevicePolicyManagerServiceShellCommand}.
- */
- static final class OwnerDto {
- public final @UserIdInt int userId;
- public final ComponentName admin;
- public final boolean isDeviceOwner;
- public final boolean isProfileOwner;
- public boolean isAffiliated;
-
- private OwnerDto(@UserIdInt int userId, ComponentName admin, boolean isDeviceOwner) {
- this.userId = userId;
- this.admin = Objects.requireNonNull(admin, "admin must not be null");
- this.isDeviceOwner = isDeviceOwner;
- this.isProfileOwner = !isDeviceOwner;
- }
- }
-
public void dump(IndentingPrintWriter pw) {
boolean needBlank = false;
if (mDeviceOwner != null) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index db6323c4100f..97e1f0b2df99 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -381,6 +381,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.connectivity.IpConnectivityMetrics";
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
"com.android.server.media.MediaCommunicationService";
+ private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
+ "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
private static final String GAME_MANAGER_SERVICE_CLASS =
@@ -1592,6 +1594,9 @@ public final class SystemServer implements Dumpable {
// all listeners have the chance to react with special handling.
Settings.Global.putInt(context.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 1);
+ } else if (context.getResources().getBoolean(R.bool.config_autoResetAirplaneMode)) {
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0);
}
StatusBarManagerService statusBar = null;
@@ -2702,6 +2707,10 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
t.traceEnd();
+ t.traceBegin("AppCompatOverridesService");
+ mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
+ t.traceEnd();
+
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
diff --git a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java
index 49d5e50e0345..d3353cd6adc7 100644
--- a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java
+++ b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java
@@ -31,6 +31,7 @@ import android.os.CancellationSignal;
import com.android.server.LocalServices;
import com.android.server.people.PeopleServiceInternal;
+import com.android.server.pm.PackageManagerService;
/**
* If a {@link ConversationStatus} is added to the system with an expiration time, remove that
@@ -50,6 +51,7 @@ public class ConversationStatusExpirationBroadcastReceiver extends BroadcastRece
final PendingIntent pi = PendingIntent.getBroadcast(context,
REQUEST_CODE,
new Intent(ACTION)
+ .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
.setData(new Uri.Builder().scheme(SCHEME)
.appendPath(getKey(userId, pkg, conversationId, status))
.build())
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index 886b2e03a104..347952bcebb0 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.pm.PackageParser.SigningDetails
import android.content.pm.PackageUserState
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
@@ -27,6 +28,7 @@ import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
import android.os.Process
import android.util.ArraySet
+import android.util.IndentingPrintWriter
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting
@@ -47,6 +49,7 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verifyNoMoreInteractions
import java.io.File
import java.util.UUID
@@ -206,6 +209,14 @@ class DomainVerificationEnforcerTest {
service(Type.QUERENT, "getInfo") {
getDomainVerificationInfo(it.targetPackageName)
},
+ service(Type.QUERENT, "printState") {
+ printState(mock(IndentingPrintWriter::class.java), null, null)
+ },
+ service(Type.QUERENT, "printStateInternal") {
+ printState(mock(IndentingPrintWriter::class.java), null, null) {
+ mockPkgSetting(it, UUID.randomUUID())
+ }
+ },
service(Type.VERIFIER, "setStatus") {
setDomainVerificationStatus(
it.targetDomainSetId,
@@ -311,6 +322,7 @@ class DomainVerificationEnforcerTest {
}
)
}
+ whenever(signingDetails) { SigningDetails.UNKNOWN }
}
fun mockPkgSetting(packageName: String, domainSetId: UUID) = spyThrowOnUnmocked(
@@ -339,6 +351,7 @@ class DomainVerificationEnforcerTest {
whenever(readUserState(1)) { PackageUserState() }
whenever(getInstantApp(anyInt())) { false }
whenever(isSystem()) { false }
+ whenever(signingDetails) { SigningDetails.UNKNOWN }
}
}
@@ -385,6 +398,7 @@ class DomainVerificationEnforcerTest {
val allowUserState = AtomicBoolean(false)
val allowPreferredApps = AtomicBoolean(false)
val allowQueryAll = AtomicBoolean(false)
+ val allowDump = AtomicBoolean(false)
val context: Context = mockThrowOnUnmocked {
initPermission(
allowUserState,
@@ -395,6 +409,7 @@ class DomainVerificationEnforcerTest {
android.Manifest.permission.SET_PREFERRED_APPLICATIONS
)
initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
+ initPermission(allowDump, android.Manifest.permission.DUMP)
}
val target = params.construct(context)
@@ -421,6 +436,10 @@ class DomainVerificationEnforcerTest {
allowQueryAll.set(true)
assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+ allowDump.set(true)
+
+ runMethod(target, NON_VERIFIER_UID)
}
private fun approvedVerifier() {
@@ -806,8 +825,12 @@ class DomainVerificationEnforcerTest {
}
val valueAsInt = value as? Int
- if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
- throw AssertionError("Expected call to return false, was $value")
+ if (valueAsInt != null) {
+ if (valueAsInt == DomainVerificationManager.STATUS_OK) {
+ throw AssertionError("Expected call to return false, was $value")
+ }
+ } else {
+ throw AssertionError("Expected call to fail")
}
} catch (e: SecurityException) {
} catch (e: PackageManager.NameNotFoundException) {
@@ -819,7 +842,7 @@ class DomainVerificationEnforcerTest {
// System/shell only
INTERNAL,
- // INTERNAL || non-legacy domain verification agent
+ // INTERNAL || non-legacy domain verification agent || DUMP permission
QUERENT,
// INTERNAL || domain verification agent
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 6c2a8916617b..19eb456a9a95 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -618,6 +618,60 @@ class DomainVerificationPackageTest {
}
@Test
+ fun migratePackageSelected() {
+ val pkgName = PKG_ONE
+ val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, SIGNATURE_ONE,
+ listOf(DOMAIN_1), listOf(DOMAIN_2))
+ val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, SIGNATURE_TWO,
+ listOf(DOMAIN_1), listOf(DOMAIN_2))
+
+ val map = mutableMapOf<String, PackageSetting>()
+ val service = makeService { map[it] }
+ service.addPackage(pkgBefore)
+
+ // Only insert the package after addPackage call to ensure the service doesn't access
+ // a live package inside the addPackage logic. It should only use the provided input.
+ map[pkgName] = pkgBefore
+
+ assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
+ .isEqualTo(DomainVerificationManager.STATUS_OK)
+
+ assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
+ .isEqualTo(DomainVerificationManager.STATUS_OK)
+
+ service.getInfo(pkgName).run {
+ assertThat(identifier).isEqualTo(UUID_ONE)
+ assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_SUCCESS,
+ ))
+ }
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+
+ // Now remove the package because migrateState shouldn't use it either
+ map.remove(pkgName)
+
+ service.migrateState(pkgBefore, pkgAfter)
+
+ map[pkgName] = pkgAfter
+
+ service.getInfo(pkgName).run {
+ assertThat(identifier).isEqualTo(UUID_TWO)
+ assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to STATE_SUCCESS,
+ ))
+ }
+ assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
+ DOMAIN_1 to DOMAIN_STATE_VERIFIED,
+ DOMAIN_2 to DOMAIN_STATE_SELECTED,
+ ))
+ assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
+ }
+
+ @Test
fun backupAndRestore() {
// This test acts as a proxy for true user restore through PackageManager,
// as that's much harder to test for real.
@@ -798,7 +852,8 @@ class DomainVerificationPackageTest {
pkgName: String,
domainSetId: UUID,
signature: String,
- domains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
+ autoVerifyDomains: List<String> = listOf(DOMAIN_1, DOMAIN_2),
+ otherDomains: List<String> = listOf(),
isSystemApp: Boolean = false
) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
@@ -806,21 +861,23 @@ class DomainVerificationPackageTest {
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
whenever(isEnabled) { true }
+ fun baseIntent(domain: String) = ParsedIntentInfo().apply {
+ addAction(Intent.ACTION_VIEW)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("http")
+ addDataScheme("https")
+ addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
+ addDataAuthority(domain, null)
+ }
+
val activityList = listOf(
ParsedActivity().apply {
- domains.forEach {
- addIntent(
- ParsedIntentInfo().apply {
- autoVerify = true
- addAction(Intent.ACTION_VIEW)
- addCategory(Intent.CATEGORY_BROWSABLE)
- addCategory(Intent.CATEGORY_DEFAULT)
- addDataScheme("http")
- addDataScheme("https")
- addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
- addDataAuthority(it, null)
- }
- )
+ autoVerifyDomains.forEach {
+ addIntent(baseIntent(it).apply { autoVerify = true })
+ }
+ otherDomains.forEach {
+ addIntent(baseIntent(it).apply { autoVerify = false })
}
},
)
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 18f1267b890f..0dd4f5b338b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -1854,6 +1855,36 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoAll_BoundByPersService_Cycle_Branch_Capability() {
+ 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, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+ MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+ bindService(client, client2, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ bindService(client2, app, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ ProcessRecord client3 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID,
+ MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false));
+ client3.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
+ bindService(app, client3, null, Context.BIND_INCLUDE_CAPABILITIES, mock(IBinder.class));
+ ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
+ lru.clear();
+ lru.add(app);
+ lru.add(client);
+ lru.add(client2);
+ lru.add(client3);
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+
+ assertEquals(PROCESS_CAPABILITY_ALL, client.mState.getSetCapability());
+ assertEquals(PROCESS_CAPABILITY_ALL, client2.mState.getSetCapability());
+ assertEquals(PROCESS_CAPABILITY_ALL, app.mState.getSetCapability());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoAll_Provider_Cycle_Branch_2() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/OWNERS b/services/tests/mockingservicestests/src/com/android/server/compat/OWNERS
new file mode 100644
index 000000000000..f8c3520e9fa8
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/compat/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
new file mode 100644
index 000000000000..df19be4a9cfe
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesParserTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.compat.overrides;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import static java.util.Collections.emptySet;
+
+import android.app.compat.PackageOverride;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test class for {@link AppCompatOverridesParser}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:AppCompatOverridesParserTest
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+@Presubmit
+public class AppCompatOverridesParserTest {
+ private static final String PACKAGE_1 = "com.android.test1";
+ private static final String PACKAGE_2 = "com.android.test2";
+ private static final String PACKAGE_3 = "com.android.test3";
+ private static final String PACKAGE_4 = "com.android.test4";
+
+ private AppCompatOverridesParser mParser;
+
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setUp() throws Exception {
+ mParser = new AppCompatOverridesParser(mPackageManager);
+ }
+
+ @Test
+ public void parseRemoveOverrides_emptyConfig_returnsEmpty() {
+ Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
+
+ assertThat(mParser.parseRemoveOverrides("", ownedChangeIds)).isEmpty();
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasWildcardNoOwnedChangeIds_returnsEmpty() {
+ when(mPackageManager.getInstalledApplications(anyInt()))
+ .thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2)));
+
+ assertThat(mParser.parseRemoveOverrides("*", /* ownedChangeIds= */ emptySet())).isEmpty();
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasWildcard_returnsAllInstalledPackagesToAllOwnedIds() {
+ Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
+ when(mPackageManager.getInstalledApplications(anyInt()))
+ .thenReturn(Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2),
+ createAppInfo(PACKAGE_3)));
+
+ Map<String, Set<Long>> result = mParser.parseRemoveOverrides("*", ownedChangeIds);
+
+ assertThat(result).hasSize(3);
+ assertThat(result.get(PACKAGE_1)).containsExactly(123L, 456L);
+ assertThat(result.get(PACKAGE_2)).containsExactly(123L, 456L);
+ assertThat(result.get(PACKAGE_3)).containsExactly(123L, 456L);
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasInvalidWildcardSymbol_returnsEmpty() {
+ Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(123L, 456L));
+ when(mPackageManager.getInstalledApplications(anyInt())).thenReturn(
+ Arrays.asList(createAppInfo(PACKAGE_1), createAppInfo(PACKAGE_2)));
+
+ assertThat(mParser.parseRemoveOverrides("**", ownedChangeIds)).isEmpty();
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasSingleEntry_returnsPackageToChangeIds() {
+ Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+ PACKAGE_1 + "=12:34", /* ownedChangeIds= */ emptySet());
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(PACKAGE_1)).containsExactly(12L, 34L);
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasMultipleEntries_returnsPackagesToChangeIds() {
+ Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L, 56L, 78L));
+
+ Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+ PACKAGE_1 + "=12," + PACKAGE_2 + "=*," + PACKAGE_3 + "=12:56:78," + PACKAGE_4
+ + "=", ownedChangeIds);
+
+ assertThat(result).hasSize(3);
+ assertThat(result.get(PACKAGE_1)).containsExactly(12L);
+ assertThat(result.get(PACKAGE_2)).containsExactly(12L, 34L, 56L, 78L);
+ assertThat(result.get(PACKAGE_3)).containsExactly(12L, 56L, 78L);
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasPackageWithWildcardNoOwnedId_returnsWithoutPackage() {
+ Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+ PACKAGE_1 + "=*," + PACKAGE_2 + "=12", /* ownedChangeIds= */ emptySet());
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(PACKAGE_2)).containsExactly(12L);
+ }
+
+ @Test
+ public void parseRemoveOverrides_configHasInvalidKeyValueListFormat_returnsEmpty() {
+ Set<Long> ownedChangeIds = new ArraySet<>(Arrays.asList(12L, 34L));
+
+ assertThat(mParser.parseRemoveOverrides(
+ PACKAGE_1 + "=12," + PACKAGE_2 + ">34", ownedChangeIds)).isEmpty();
+ }
+
+
+ @Test
+ public void parseRemoveOverrides_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() {
+ Map<String, Set<Long>> result = mParser.parseRemoveOverrides(
+ PACKAGE_1 + "=12," + PACKAGE_2 + "=12:56L:78," + PACKAGE_3
+ + "=34L", /* ownedChangeIds= */ emptySet());
+
+ assertThat(result).hasSize(2);
+ assertThat(result.get(PACKAGE_1)).containsExactly(12L);
+ assertThat(result.get(PACKAGE_2)).containsExactly(12L, 78L);
+ }
+
+ @Test
+ public void parseOwnedChangeIds_emptyConfig_returnsEmpty() {
+ assertThat(AppCompatOverridesParser.parseOwnedChangeIds("")).isEmpty();
+ }
+
+ @Test
+ public void parseOwnedChangeIds_configHasSingleChangeId_returnsChangeId() {
+ assertThat(AppCompatOverridesParser.parseOwnedChangeIds("123")).containsExactly(123L);
+ }
+
+ @Test
+ public void parseOwnedChangeIds_configHasMultipleChangeIds_returnsChangeIds() {
+ assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,34,56")).containsExactly(12L,
+ 34L, 56L);
+ }
+
+ @Test
+ public void parseOwnedChangeIds_configHasInvalidChangeIds_returnsWithoutInvalidChangeIds() {
+ // We add a valid entry before and after the invalid ones to make sure they are applied.
+ assertThat(AppCompatOverridesParser.parseOwnedChangeIds("12,C34,56")).containsExactly(12L,
+ 56L);
+ }
+
+ @Test
+ public void parsePackageOverrides_emptyConfigNoOwnedChangeIds_returnsEmpty() {
+ Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+ /* configStr= */ "", /* versionCode= */ 0, /* changeIdsToSkip= */ emptySet());
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void parsePackageOverrides_configWithSingleOverride_returnsOverride() {
+ Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+ /* configStr= */ "123:::true", /* versionCode= */ 5, /* changeIdsToSkip= */
+ emptySet());
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(123L)).isEqualTo(
+ new PackageOverride.Builder().setEnabled(true).build());
+ }
+
+ @Test
+ public void parsePackageOverrides_configWithMultipleOverrides_returnsOverrides() {
+ Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+ /* configStr= */ "910:3:4:false,78:10::false,12:::false,34:1:2:true,34:10::true,"
+ + "56::2:true,56:3:4:false,34:4:8:true,78:6:7:true,910:5::true,"
+ + "1112::5:true,56:6::true,1112:6:7:false", /* versionCode= */
+ 5, /* changeIdsToSkip= */ emptySet());
+
+ assertThat(result).hasSize(6);
+ assertThat(result.get(12L)).isEqualTo(
+ new PackageOverride.Builder().setEnabled(false).build());
+ assertThat(result.get(34L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(4).setMaxVersionCode(8).setEnabled(
+ true).build());
+ assertThat(result.get(56L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(3).setMaxVersionCode(4).setEnabled(
+ false).build());
+ assertThat(result.get(78L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(6).setMaxVersionCode(7).setEnabled(
+ true).build());
+ assertThat(result.get(910L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(5).setEnabled(true).build());
+ assertThat(result.get(1112L)).isEqualTo(
+ new PackageOverride.Builder().setMaxVersionCode(5).setEnabled(true).build());
+ }
+
+ @Test
+ public void parsePackageOverrides_changeIdsToSkipSpecified_returnsWithoutChangeIdsToSkip() {
+ ArraySet<Long> changeIdsToSkip = new ArraySet<>(Arrays.asList(34L, 56L));
+ Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+ /* configStr= */ "12:::true,56:3:7:true", /* versionCode= */ 5, changeIdsToSkip);
+
+ assertThat(result).hasSize(1);
+ assertThat(result.get(12L)).isEqualTo(
+ new PackageOverride.Builder().setEnabled(true).build());
+ }
+
+ @Test
+ public void parsePackageOverrides_changeIdsToSkipContainsAllIds_returnsEmpty() {
+ ArraySet<Long> changeIdsToSkip = new ArraySet<>(Arrays.asList(12L, 34L));
+ Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+ /* configStr= */ "12:::true", /* versionCode= */ 5, changeIdsToSkip);
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void parsePackageOverrides_someOverridesAreInvalid_returnsWithoutInvalidOverrides() {
+ // We add a valid entry before and after the invalid ones to make sure they are applied.
+ Map<Long, PackageOverride> result = AppCompatOverridesParser.parsePackageOverrides(
+ /* configStr= */ "12:::True,56:1:2:FALSE,56:3:true,78:4:8:true:,C1:::true,910:::no,"
+ + "1112:1:ten:true,1112:one:10:true,,1314:7:3:false,34:::",
+ /* versionCode= */ 5, /* changeIdsToSkip= */ emptySet());
+
+ assertThat(result).hasSize(2);
+ assertThat(result.get(12L)).isEqualTo(
+ new PackageOverride.Builder().setEnabled(true).build());
+ assertThat(result.get(56L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(1).setMaxVersionCode(2).setEnabled(
+ false).build());
+ }
+
+ private static ApplicationInfo createAppInfo(String packageName) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.packageName = packageName;
+ return appInfo;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
new file mode 100644
index 000000000000..007191f02631
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java
@@ -0,0 +1,726 @@
+/*
+ * 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.compat.overrides;
+
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.ACTION_USER_SWITCHED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_OWNED_CHANGE_IDS;
+import static com.android.server.compat.overrides.AppCompatOverridesParser.FLAG_REMOVE_OVERRIDES;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.compat.PackageOverride;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.compat.CompatibilityOverrideConfig;
+import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.server.testables.TestableDeviceConfig.TestableDeviceConfigRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Test class for {@link AppCompatOverridesService}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:AppCompatOverridesServiceTest
+ */
+@RunWith(MockitoJUnitRunner.class)
+@SmallTest
+@Presubmit
+public class AppCompatOverridesServiceTest {
+ private static final String NAMESPACE_1 = "namespace_1";
+ private static final String NAMESPACE_2 = "namespace_2";
+ private static final String NAMESPACE_3 = "namespace_3";
+ private static final List<String> SUPPORTED_NAMESPACES = Arrays.asList(NAMESPACE_1,
+ NAMESPACE_2, NAMESPACE_3);
+
+ private static final String PACKAGE_1 = "com.android.test1";
+ private static final String PACKAGE_2 = "com.android.test2";
+ private static final String PACKAGE_3 = "com.android.test3";
+ private static final String PACKAGE_4 = "com.android.test4";
+
+ private MockContext mMockContext;
+ private BroadcastReceiver mPackageReceiver;
+ private AppCompatOverridesService mService;
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private IPlatformCompat mPlatformCompat;
+
+ @Captor
+ private ArgumentCaptor<CompatibilityOverrideConfig> mOverridesToAddConfigCaptor;
+ @Captor
+ private ArgumentCaptor<CompatibilityOverridesToRemoveConfig> mOverridesToRemoveConfigCaptor;
+
+ @Rule
+ public TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfigRule();
+
+ class MockContext extends ContextWrapper {
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public Executor getMainExecutor() {
+ // Run on current thread
+ return Runnable::run;
+ }
+
+ @Override
+ @Nullable
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ mPackageReceiver = receiver;
+ return null;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mMockContext = new MockContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext());
+ mService = new AppCompatOverridesService(mMockContext, mPlatformCompat,
+ SUPPORTED_NAMESPACES);
+ mService.registerPackageReceiver();
+ assertThat(mPackageReceiver).isNotNull();
+ }
+
+ @Test
+ public void onPropertiesChanged_removeOverridesFlagNotSet_appliesPackageOverrides()
+ throws Exception {
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 3);
+ mockGetApplicationInfoNotInstalled(PACKAGE_2);
+ mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 10);
+ mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 1);
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
+ .setString(PACKAGE_1, "123:::true,456::1:false,456:2::true,789:::false")
+ .setString(PACKAGE_2, "123:::true")
+ .setString(PACKAGE_3, "123:1:9:true,123:10:11:false,123:11::true")
+ .setString(PACKAGE_4, "").build());
+
+ Map<Long, PackageOverride> addedOverrides;
+ // Package 1
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides;
+ assertThat(addedOverrides).hasSize(3);
+ assertThat(addedOverrides.get(123L)).isEqualTo(
+ new PackageOverride.Builder().setEnabled(true).build());
+ assertThat(addedOverrides.get(456L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build());
+ assertThat(addedOverrides.get(789L)).isEqualTo(
+ new PackageOverride.Builder().setEnabled(false).build());
+ // Package 2
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
+ // Package 3
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_3));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
+ addedOverrides = mOverridesToAddConfigCaptor.getValue().overrides;
+ assertThat(addedOverrides).hasSize(1);
+ assertThat(addedOverrides.get(123L)).isEqualTo(
+ new PackageOverride.Builder().setMinVersionCode(10).setMaxVersionCode(
+ 11).setEnabled(false).build());
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L, 789L);
+ // Package 4
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_4));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_4));
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L,
+ 789L);
+ }
+
+ @Test
+ public void onPropertiesChanged_ownedChangeIdsFlagNotSet_onlyAddsOverrides()
+ throws Exception {
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "123:::true").build());
+
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L);
+ }
+
+ @Test
+ public void onPropertiesChanged_removeOverridesFlagSetBefore_skipsOverridesToRemove()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
+ .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456," + PACKAGE_2 + "=123")
+ .setString(PACKAGE_1, "123:::true")
+ .setString(PACKAGE_4, "123:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "123:::true,789:::false")
+ .setString(PACKAGE_2, "123:::true")
+ .setString(PACKAGE_3, "456:::true").build());
+
+ // Package 1
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(789L);
+ // Package 2
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L, 789L);
+ // Package 3
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_3));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L);
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 789L);
+ // Package 4 (not applied because it hasn't changed after the listener was added)
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_4));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
+ }
+
+ @Test
+ public void onPropertiesChanged_removeOverridesFlagChangedNoPackageOverridesFlags_removesOnly()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
+ .setString(PACKAGE_1, "")
+ .setString(PACKAGE_2, "").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_REMOVE_OVERRIDES,
+ PACKAGE_1 + "=123:456," + PACKAGE_2 + "=*").build());
+
+ // Package 1
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+ List<CompatibilityOverridesToRemoveConfig> configs =
+ mOverridesToRemoveConfigCaptor.getAllValues();
+ assertThat(configs.size()).isAtLeast(2);
+ assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(123L, 456L);
+ assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(789L);
+ // Package 2
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L,
+ 789L);
+ }
+
+ @Test
+ public void onPropertiesChanged_removeOverridesFlagAndSomePackageOverrideFlagsChanged_ok()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
+ .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=123:456")
+ .setString(PACKAGE_1, "123:::true,789:::false")
+ .setString(PACKAGE_3, "456:::false,789:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_2 + "=123," + PACKAGE_3 + "=789")
+ .setString(PACKAGE_2, "123:::true").build());
+
+ // Package 1
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_1));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L,
+ 789L);
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L);
+ // Package 2
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+ verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
+ List<CompatibilityOverridesToRemoveConfig> configs =
+ mOverridesToRemoveConfigCaptor.getAllValues();
+ assertThat(configs.size()).isAtLeast(2);
+ assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(123L);
+ assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(456L, 789L);
+ // Package 3
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_3));
+ verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_3));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(456L);
+ configs = mOverridesToRemoveConfigCaptor.getAllValues();
+ assertThat(configs.size()).isAtLeast(2);
+ assertThat(configs.get(configs.size() - 2).changeIds).containsExactly(789L);
+ assertThat(configs.get(configs.size() - 1).changeIds).containsExactly(123L);
+ }
+
+ @Test
+ public void onPropertiesChanged_ownedChangeIdsFlagAndSomePackageOverrideFlagsChanged_ok()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=*")
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456")
+ .setString(PACKAGE_1, "123:::true")
+ .setString(PACKAGE_3, "456:::false").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456,789")
+ .setString(PACKAGE_2, "123:::true").build());
+
+ // Package 1
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(123L, 456L,
+ 789L);
+ // Package 2
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(mOverridesToAddConfigCaptor.capture(),
+ eq(PACKAGE_2));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_2));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(123L);
+ assertThat(mOverridesToRemoveConfigCaptor.getValue().changeIds).containsExactly(456L, 789L);
+ // Package 3
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_3));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+ }
+
+ @Test
+ public void onPropertiesChanged_platformCompatThrowsExceptionForSomeCalls_skipsFailedCalls()
+ throws Exception {
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_2, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_3, /* versionCode= */ 0);
+ mockGetApplicationInfo(PACKAGE_4, /* versionCode= */ 0);
+ doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_2));
+ doThrow(new RemoteException()).when(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+
+ mService.registerDeviceConfigListeners();
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "123,456")
+ .setString(PACKAGE_1, "123:::true")
+ .setString(PACKAGE_2, "123:::true")
+ .setString(PACKAGE_3, "123:::true")
+ .setString(PACKAGE_4, "123:::true").build());
+
+ // Package 1
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+ eq(PACKAGE_1));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ // Package 2
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+ eq(PACKAGE_2));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_2));
+ // Package 3
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+ eq(PACKAGE_3));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_3));
+ // Package 4
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(any(CompatibilityOverrideConfig.class),
+ eq(PACKAGE_1));
+ verify(mPlatformCompat).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_4));
+ }
+
+ @Test
+ public void packageReceiver_packageAddedIntentDataIsNull_doesNothing() throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext, new Intent(ACTION_PACKAGE_ADDED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_actionIsNull_doesNothing() throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, /* action= */ null));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_unsupportedAction_doesNothing() throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_USER_SWITCHED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_packageAddedIntentPackageNotInstalled_doesNothing()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::true").build());
+ mockGetApplicationInfoNotInstalled(PACKAGE_1);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_packageAddedIntentNoOverridesForPackage_doesNothing()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_2, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_3, "201:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_packageAddedIntent_appliesOverridesFromAllNamespaces()
+ throws Exception {
+ // We're adding the owned_change_ids flag to make sure it's ignored.
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103")
+ .setString(PACKAGE_1, "101:::true")
+ .setString(PACKAGE_2, "102:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(FLAG_OWNED_CHANGE_IDS, "201,202,203")
+ .setString(PACKAGE_3, "201:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3)
+ .setString(FLAG_OWNED_CHANGE_IDS, "301,302")
+ .setString(PACKAGE_1, "301:::true,302:::false")
+ .setString(PACKAGE_2, "302:::false").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED));
+
+ verify(mPlatformCompat, times(2)).putOverridesOnReleaseBuilds(
+ mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ List<CompatibilityOverrideConfig> configs = mOverridesToAddConfigCaptor.getAllValues();
+ assertThat(configs.get(0).overrides.keySet()).containsExactly(101L);
+ assertThat(configs.get(1).overrides.keySet()).containsExactly(301L, 302L);
+ }
+
+ @Test
+ public void packageReceiver_packageChangedIntent_appliesOverrides()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "101:::true,103:::false").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_CHANGED));
+
+ verify(mPlatformCompat).putOverridesOnReleaseBuilds(
+ mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ assertThat(mOverridesToAddConfigCaptor.getValue().overrides.keySet()).containsExactly(101L,
+ 103L);
+ }
+
+ @Test
+ public void packageReceiver_packageAddedIntentRemoveOverridesSetForSomeNamespaces_skipsIds()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=103," + PACKAGE_2 + "=101")
+ .setString(PACKAGE_1, "101:::true,103:::false")
+ .setString(PACKAGE_2, "102:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3)
+ .setString(FLAG_REMOVE_OVERRIDES, PACKAGE_1 + "=301," + PACKAGE_3 + "=302")
+ .setString(PACKAGE_1, "301:::true,302:::false,303:::true")
+ .setString(PACKAGE_3, "302:::false").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED));
+
+ verify(mPlatformCompat, times(3)).putOverridesOnReleaseBuilds(
+ mOverridesToAddConfigCaptor.capture(), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ List<CompatibilityOverrideConfig> configs = mOverridesToAddConfigCaptor.getAllValues();
+ assertThat(configs.get(0).overrides.keySet()).containsExactly(101L);
+ assertThat(configs.get(1).overrides.keySet()).containsExactly(201L);
+ assertThat(configs.get(2).overrides.keySet()).containsExactly(302L, 303L);
+ }
+
+ @Test
+ public void packageReceiver_packageRemovedIntentNoOverridesForPackage_doesNothing()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "101,102")
+ .setString(PACKAGE_2, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(FLAG_OWNED_CHANGE_IDS, "201,202")
+ .setString(PACKAGE_3, "201:::true").build());
+ mockGetApplicationInfoNotInstalled(PACKAGE_1);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_packageRemovedIntentPackageInstalledForAnotherUser_doesNothing()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103")
+ .setString(PACKAGE_1, "101:::true,103:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(FLAG_OWNED_CHANGE_IDS, "201,202")
+ .setString(PACKAGE_1, "202:::false").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ @Test
+ public void packageReceiver_packageRemovedIntent_removesOwnedOverridesForNamespacesWithPackage()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "101,102,103")
+ .setString(PACKAGE_1, "101:::true,103:::false")
+ .setString(PACKAGE_2, "102:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(FLAG_OWNED_CHANGE_IDS, "201")
+ .setString(PACKAGE_3, "201:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3)
+ .setString(FLAG_OWNED_CHANGE_IDS, "301,302")
+ .setString(PACKAGE_1, "302:::false")
+ .setString(PACKAGE_2, "301:::true").build());
+ mockGetApplicationInfoNotInstalled(PACKAGE_1);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+ List<CompatibilityOverridesToRemoveConfig> configs =
+ mOverridesToRemoveConfigCaptor.getAllValues();
+ assertThat(configs.get(0).changeIds).containsExactly(101L, 102L, 103L);
+ assertThat(configs.get(1).changeIds).containsExactly(301L, 302L);
+ }
+
+ @Test
+ public void packageReceiver_packageRemovedIntentNoOwnedIdsForSomeNamespace_skipsNamespace()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(FLAG_OWNED_CHANGE_IDS, "101,102")
+ .setString(PACKAGE_1, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3)
+ .setString(FLAG_OWNED_CHANGE_IDS, "301")
+ .setString(PACKAGE_1, "301:::true").build());
+ mockGetApplicationInfoNotInstalled(PACKAGE_1);
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_REMOVED));
+
+ verify(mPlatformCompat, never()).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, times(2)).removeOverridesOnReleaseBuilds(
+ mOverridesToRemoveConfigCaptor.capture(), eq(PACKAGE_1));
+ List<CompatibilityOverridesToRemoveConfig> configs =
+ mOverridesToRemoveConfigCaptor.getAllValues();
+ assertThat(configs.get(0).changeIds).containsExactly(101L, 102L);
+ assertThat(configs.get(1).changeIds).containsExactly(301L);
+ }
+
+ @Test
+ public void packageReceiver_platformCompatThrowsExceptionForSomeNamespace_skipsFailedCall()
+ throws Exception {
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_1)
+ .setString(PACKAGE_1, "101:::true").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_2)
+ .setString(PACKAGE_1, "201:::false").build());
+ DeviceConfig.setProperties(new Properties.Builder(NAMESPACE_3)
+ .setString(PACKAGE_1, "301:::true").build());
+ mockGetApplicationInfo(PACKAGE_1, /* versionCode= */ 0);
+ doThrow(new RemoteException()).when(mPlatformCompat).putOverridesOnReleaseBuilds(
+ argThat(config -> config.overrides.containsKey(201L)), eq(PACKAGE_1));
+
+ mPackageReceiver.onReceive(mMockContext,
+ createPackageIntent(PACKAGE_1, ACTION_PACKAGE_ADDED));
+
+ verify(mPlatformCompat, times(3)).putOverridesOnReleaseBuilds(
+ any(CompatibilityOverrideConfig.class), eq(PACKAGE_1));
+ verify(mPlatformCompat, never()).removeOverridesOnReleaseBuilds(
+ any(CompatibilityOverridesToRemoveConfig.class), eq(PACKAGE_1));
+ }
+
+ private void mockGetApplicationInfo(String packageName, long versionCode)
+ throws Exception {
+ when(mPackageManager.getApplicationInfo(eq(packageName), anyInt())).thenReturn(
+ createAppInfo(versionCode));
+ }
+
+ private void mockGetApplicationInfoNotInstalled(String packageName) throws Exception {
+ when(mPackageManager.getApplicationInfo(eq(packageName), anyInt()))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ }
+
+ private static ApplicationInfo createAppInfo(long versionCode) {
+ ApplicationInfo appInfo = new ApplicationInfo();
+ appInfo.longVersionCode = versionCode;
+ return appInfo;
+ }
+
+ private Intent createPackageIntent(String packageName, @Nullable String action) {
+ return new Intent(action, Uri.parse("package:" + packageName));
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS
new file mode 100644
index 000000000000..6e8aefc4bc01
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/compat/overrides/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
index 589a3497435e..457c8db9fdf3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/FactoryResetterTest.java
@@ -48,7 +48,7 @@ import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
/**
- * Run it as {@code atest FrameworksMockingCoreTests:FactoryResetterTest}
+ * Run it as {@code atest FrameworksMockingServicesTests:FactoryResetterTest}
*/
@Presubmit
public final class FactoryResetterTest {
diff --git a/services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java
new file mode 100644
index 000000000000..dd67d7208034
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/devicepolicy/OwnerShellDataTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.devicepolicy;
+
+import static android.os.UserHandle.USER_NULL;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.expectThrows;
+
+import android.content.ComponentName;
+
+import org.junit.Test;
+
+/**
+ * Run it as {@code atest FrameworksMockingServicesTests:OwnerShellDataTest}
+ */
+public final class OwnerShellDataTest {
+
+ private static final int USER_ID = 007;
+ private static final int PARENT_USER_ID = 'M' + 'I' + 6;
+ private static final ComponentName ADMIN = new ComponentName("Bond", "James");
+
+ @Test
+ public void testForDeviceOwner_noAdmin() {
+ expectThrows(NullPointerException.class,
+ () -> OwnerShellData.forDeviceOwner(USER_ID, /* admin= */ null));
+ }
+
+ @Test
+ public void testForDeviceOwner_invalidUser() {
+ expectThrows(IllegalArgumentException.class,
+ () -> OwnerShellData.forDeviceOwner(USER_NULL, ADMIN));
+ }
+
+ @Test
+ public void testForDeviceOwner() {
+ OwnerShellData dto = OwnerShellData.forDeviceOwner(USER_ID, ADMIN);
+
+ assertWithMessage("dto(%s).userId", dto).that(dto.userId).isEqualTo(USER_ID);
+ assertWithMessage("dto(%s).parentUserId", dto).that(dto.parentUserId)
+ .isEqualTo(USER_NULL);
+ assertWithMessage("dto(%s).admin", dto).that(dto.admin).isSameInstanceAs(ADMIN);
+ assertWithMessage("dto(%s).isDeviceOwner", dto).that(dto.isDeviceOwner).isTrue();
+ assertWithMessage("dto(%s).isProfileOwner", dto).that(dto.isProfileOwner).isFalse();
+ assertWithMessage("dto(%s).isManagedProfileOwner", dto).that(dto.isManagedProfileOwner)
+ .isFalse();
+ assertWithMessage("dto(%s).isAffiliated", dto).that(dto.isAffiliated).isFalse();
+ }
+
+ @Test
+ public void testForUserProfileOwner_noAdmin() {
+ expectThrows(NullPointerException.class,
+ () -> OwnerShellData.forUserProfileOwner(USER_ID, /* admin= */ null));
+ }
+
+ @Test
+ public void testForUserProfileOwner_invalidUser() {
+ expectThrows(IllegalArgumentException.class,
+ () -> OwnerShellData.forUserProfileOwner(USER_NULL, ADMIN));
+ }
+
+ @Test
+ public void testForUserProfileOwner() {
+ OwnerShellData dto = OwnerShellData.forUserProfileOwner(USER_ID, ADMIN);
+
+ assertWithMessage("dto(%s).userId", dto).that(dto.userId).isEqualTo(USER_ID);
+ assertWithMessage("dto(%s).parentUserId", dto).that(dto.parentUserId)
+ .isEqualTo(USER_NULL);
+ assertWithMessage("dto(%s).admin", dto).that(dto.admin).isSameInstanceAs(ADMIN);
+ assertWithMessage("dto(%s).isDeviceOwner", dto).that(dto.isDeviceOwner).isFalse();
+ assertWithMessage("dto(%s).isProfileOwner", dto).that(dto.isProfileOwner).isTrue();
+ assertWithMessage("dto(%s).isManagedProfileOwner", dto).that(dto.isManagedProfileOwner)
+ .isFalse();
+ assertWithMessage("dto(%s).isAffiliated", dto).that(dto.isAffiliated).isFalse();
+ }
+
+ @Test
+ public void testForManagedProfileOwner_noAdmin() {
+ expectThrows(NullPointerException.class,
+ () -> OwnerShellData.forManagedProfileOwner(USER_ID, PARENT_USER_ID, null));
+ }
+
+ @Test
+ public void testForManagedProfileOwner_invalidUser() {
+ expectThrows(IllegalArgumentException.class,
+ () -> OwnerShellData.forManagedProfileOwner(USER_NULL, PARENT_USER_ID, ADMIN));
+ }
+
+ @Test
+ public void testForManagedProfileOwner_invalidParent() {
+ expectThrows(IllegalArgumentException.class,
+ () -> OwnerShellData.forManagedProfileOwner(USER_ID, USER_NULL, ADMIN));
+ }
+
+ @Test
+ public void testForManagedProfileOwner_parentOfItself() {
+ expectThrows(IllegalArgumentException.class,
+ () -> OwnerShellData.forManagedProfileOwner(USER_ID, USER_ID, ADMIN));
+ }
+
+ @Test
+ public void testForManagedProfileOwner() {
+ OwnerShellData dto = OwnerShellData.forManagedProfileOwner(USER_ID, PARENT_USER_ID, ADMIN);
+
+ assertWithMessage("dto(%s).userId", dto).that(dto.userId).isEqualTo(USER_ID);
+ assertWithMessage("dto(%s).parentUserId", dto).that(dto.parentUserId)
+ .isEqualTo(PARENT_USER_ID);
+ assertWithMessage("dto(%s).admin", dto).that(dto.admin).isSameInstanceAs(ADMIN);
+ assertWithMessage("dto(%s).isDeviceOwner", dto).that(dto.isDeviceOwner).isFalse();
+ assertWithMessage("dto(%s).isProfileOwner", dto).that(dto.isProfileOwner).isFalse();
+ assertWithMessage("dto(%s).isManagedProfileOwner", dto).that(dto.isManagedProfileOwner)
+ .isTrue();
+ assertWithMessage("dto(%s).isAffiliated", dto).that(dto.isAffiliated).isFalse();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 0efcc57eeec2..28cdd6317e5a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -134,6 +134,20 @@ public class LocalDisplayAdapterTest {
when(mMockedResources.getFloat(com.android.internal.R.dimen
.config_screenBrightnessSettingMaximumFloat))
.thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]);
+ when(mMockedResources.getStringArray(R.array.config_displayUniqueIdArray))
+ .thenReturn(new String[]{});
+ TypedArray mockArray = mock(TypedArray.class);
+ when(mockArray.length()).thenReturn(0);
+ when(mMockedResources.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_waterfallCutoutArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerRadiusArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerTopRadiusArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_roundedCornerBottomRadiusArray))
+ .thenReturn(mockArray);
}
@After
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
index e2e7f5dd7ba2..94dcdf92d9d4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
@@ -58,72 +58,86 @@ public class LocationAttributionHelperTest {
@Test
public void testLocationMonitoring() {
CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null);
- Object key1 = new Object();
- Object key2 = new Object();
CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null);
- Object key3 = new Object();
- Object key4 = new Object();
-
- mHelper.reportLocationStart(caller1, "gps", key1);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
-
- mHelper.reportLocationStart(caller1, "gps", key2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
-
- mHelper.reportLocationStart(caller2, "gps", key3);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
-
- mHelper.reportLocationStart(caller2, "gps", key4);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
-
- mHelper.reportLocationStop(caller1, "gps", key2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1);
- mHelper.reportLocationStop(caller1, "gps", key1);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1);
-
- mHelper.reportLocationStop(caller2, "gps", key3);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2);
- mHelper.reportLocationStop(caller2, "gps", key4);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2);
+
+ mHelper.reportLocationStart(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+
+ mHelper.reportLocationStart(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+
+ mHelper.reportLocationStart(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+
+ mHelper.reportLocationStart(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+
+ mHelper.reportLocationStop(caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ mHelper.reportLocationStop(caller1);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller1));
+
+ mHelper.reportLocationStop(caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+ mHelper.reportLocationStop(caller2);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller2));
}
@Test
public void testHighPowerLocationMonitoring() {
CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null);
- Object key1 = new Object();
- Object key2 = new Object();
CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null);
- Object key3 = new Object();
- Object key4 = new Object();
-
- mHelper.reportHighPowerLocationStart(caller1, "gps", key1);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
-
- mHelper.reportHighPowerLocationStart(caller1, "gps", key2);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
-
- mHelper.reportHighPowerLocationStart(caller2, "gps", key3);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
-
- mHelper.reportHighPowerLocationStart(caller2, "gps", key4);
- verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
-
- mHelper.reportHighPowerLocationStop(caller1, "gps", key2);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
- mHelper.reportHighPowerLocationStop(caller1, "gps", key1);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1);
-
- mHelper.reportHighPowerLocationStop(caller2, "gps", key3);
- verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
- mHelper.reportHighPowerLocationStop(caller2, "gps", key4);
- verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2);
+
+ mHelper.reportHighPowerLocationStart(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+
+ mHelper.reportHighPowerLocationStart(caller1);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+
+ mHelper.reportHighPowerLocationStart(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+
+ mHelper.reportHighPowerLocationStart(caller2);
+ verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+
+ mHelper.reportHighPowerLocationStop(caller1);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+ mHelper.reportHighPowerLocationStop(caller1);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller1));
+
+ mHelper.reportHighPowerLocationStop(caller2);
+ verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller2));
+ mHelper.reportHighPowerLocationStop(caller2);
+ verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
+ CallerIdentity.forAggregation(caller2));
}
}
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 d0b2edadc714..890a5495ef16 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
@@ -845,6 +845,48 @@ public class LocationProviderManagerTest {
}
@Test
+ public void testLocationMonitoring_multipleIdentities() {
+ CallerIdentity identity1 = CallerIdentity.forTest(CURRENT_USER, 1,
+ "mypackage", "attribution", "listener1");
+ CallerIdentity identity2 = CallerIdentity.forTest(CURRENT_USER, 1,
+ "mypackage", "attribution", "listener2");
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ IDENTITY.getPackageName())).isFalse();
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(0).setWorkSource(
+ WORK_SOURCE).build();
+ mManager.registerLocationRequest(request1, identity1, PERMISSION_FINE, listener1);
+
+ ILocationListener listener2 = createMockLocationListener();
+ LocationRequest request2 = new LocationRequest.Builder(0).setWorkSource(
+ WORK_SOURCE).build();
+ mManager.registerLocationRequest(request2, identity2, PERMISSION_FINE, listener2);
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ "mypackage")).isTrue();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ "mypackage")).isTrue();
+
+ mManager.unregisterLocationRequest(listener2);
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ "mypackage")).isTrue();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ "mypackage")).isTrue();
+
+ mManager.unregisterLocationRequest(listener1);
+
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION,
+ "mypackage")).isFalse();
+ assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION,
+ "mypackage")).isFalse();
+ }
+
+ @Test
public void testProviderRequest() {
assertThat(mProvider.getRequest().isActive()).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 4d6f49e5d223..4eba21934a4e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,6 +90,19 @@ public class StationaryThrottlingLocationProviderTest {
}
@Test
+ public void testThrottle_lowInterval() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(0).build();
+
+ mProvider.getController().setRequest(request);
+ mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+ verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mListener, after(1500).times(2)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
public void testThrottle_stationaryExit() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
@@ -104,17 +117,16 @@ public class StationaryThrottlingLocationProviderTest {
mInjector.getDeviceIdleHelper().setIdle(true);
verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
- verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
- verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
mInjector.getDeviceStationaryHelper().setStationary(false);
verify(mDelegate, times(2)).onSetRequest(request);
- verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+ verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
}
@Test
public void testThrottle_idleExit() {
- ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
@@ -127,17 +139,16 @@ public class StationaryThrottlingLocationProviderTest {
mInjector.getDeviceStationaryHelper().setStationary(true);
verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
- verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
- verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
mInjector.getDeviceIdleHelper().setIdle(false);
verify(mDelegate, times(2)).onSetRequest(request);
- verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+ verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
}
@Test
public void testThrottle_NoInitialLocation() {
- ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build();
mProvider.getController().setRequest(request);
verify(mDelegate).onSetRequest(request);
@@ -149,11 +160,11 @@ public class StationaryThrottlingLocationProviderTest {
mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
- verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
mInjector.getDeviceStationaryHelper().setStationary(false);
verify(mDelegate, times(2)).onSetRequest(request);
- verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
+ verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index 72bc77ebb9ef..e053dc3fd32e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -16,8 +16,10 @@
package com.android.server.pm
+import android.content.Context
import android.os.Build
import android.os.Handler
+import android.os.PowerManager
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
import android.testing.AndroidTestingRunner
@@ -27,6 +29,7 @@ import com.android.server.apphibernation.AppHibernationManagerInternal
import com.android.server.apphibernation.AppHibernationService
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.testutils.whenever
+import org.junit.Assert
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -55,6 +58,8 @@ class PackageManagerServiceHibernationTests {
@Mock
lateinit var appHibernationManager: AppHibernationManagerInternal
+ @Mock
+ lateinit var powerManager: PowerManager
@Before
@Throws(Exception::class)
@@ -68,6 +73,24 @@ class PackageManagerServiceHibernationTests {
.thenReturn(appHibernationManager)
whenever(rule.mocks().injector.handler)
.thenReturn(Handler(TestableLooper.get(this).looper))
+ val injector = object : PackageDexOptimizer.Injector {
+ override fun getAppHibernationManagerInternal(): AppHibernationManagerInternal {
+ return appHibernationManager
+ }
+
+ override fun getPowerManager(context: Context?): PowerManager {
+ return powerManager
+ }
+ }
+ val packageDexOptimizer = PackageDexOptimizer(
+ injector,
+ rule.mocks().installer,
+ rule.mocks().installLock,
+ rule.mocks().context,
+ "*dexopt*")
+ whenever(rule.mocks().injector.packageDexOptimizer)
+ .thenReturn(packageDexOptimizer)
+ whenever(appHibernationManager.isOatArtifactDeletionEnabled).thenReturn(true)
}
@Test
@@ -78,8 +101,11 @@ class PackageManagerServiceHibernationTests {
rule.system().dataAppDirectory)
val pm = createPackageManagerService()
rule.system().validateFinalState()
- val ps = pm.getPackageSetting(TEST_PACKAGE_NAME)
- ps!!.setStopped(true, TEST_USER_ID)
+
+ TestableLooper.get(this).processAllMessages()
+
+ whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID))
+ .thenReturn(true)
pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
@@ -90,6 +116,31 @@ class PackageManagerServiceHibernationTests {
}
@Test
+ fun testExitForceStop_nonExistingAppHibernationManager_doesNotThrowException() {
+ whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
+ .thenReturn(null)
+
+ rule.system().stageScanExistingPackage(
+ TEST_PACKAGE_NAME,
+ 1L,
+ rule.system().dataAppDirectory)
+ val pm = createPackageManagerService()
+ rule.system().validateFinalState()
+
+ TestableLooper.get(this).processAllMessages()
+
+ whenever(appHibernationManager.isHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID))
+ .thenReturn(true)
+
+ try {
+ pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
+ TestableLooper.get(this).processAllMessages()
+ } catch (e: Exception) {
+ Assert.fail("Method throws exception when AppHibernationManager is not ready.\n$e")
+ }
+ }
+
+ @Test
fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() {
rule.system().stageScanExistingPackage(
TEST_PACKAGE_NAME,
diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java
index 64b24c12f046..43188f630729 100644
--- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java
+++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java
@@ -95,18 +95,25 @@ public final class TestableDeviceConfig implements StaticMockFixture {
String name = invocationOnMock.getArgument(1);
String value = invocationOnMock.getArgument(2);
mKeyValueMap.put(getKey(namespace, name), value);
- for (DeviceConfig.OnPropertiesChangedListener listener :
- mOnPropertiesChangedListenerMap.keySet()) {
- if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) {
- mOnPropertiesChangedListenerMap.get(listener).second.execute(
- () -> listener.onPropertiesChanged(
- getProperties(namespace, name, value)));
- }
- }
+ invokeListeners(namespace, getProperties(namespace, name, value));
return true;
}
).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean()));
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ Properties properties = invocationOnMock.getArgument(0);
+ String namespace = properties.getNamespace();
+ Map<String, String> keyValues = new ArrayMap<>();
+ for (String name : properties.getKeyset()) {
+ String value = properties.getString(name, /* defaultValue= */ "");
+ mKeyValueMap.put(getKey(namespace, name), value);
+ keyValues.put(name.toLowerCase(), value);
+ }
+ invokeListeners(namespace, getProperties(namespace, keyValues));
+ return true;
+ }
+ ).when(() -> DeviceConfig.setProperties(any(Properties.class)));
+
doAnswer((Answer<String>) invocationOnMock -> {
String namespace = invocationOnMock.getArgument(0);
String name = invocationOnMock.getArgument(1);
@@ -153,6 +160,16 @@ public final class TestableDeviceConfig implements StaticMockFixture {
return Pair.create(values[0], values[1]);
}
+ private void invokeListeners(String namespace, Properties properties) {
+ for (DeviceConfig.OnPropertiesChangedListener listener :
+ mOnPropertiesChangedListenerMap.keySet()) {
+ if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) {
+ mOnPropertiesChangedListenerMap.get(listener).second.execute(
+ () -> listener.onPropertiesChanged(properties));
+ }
+ }
+ }
+
private Properties getProperties(String namespace, String name, String value) {
return getProperties(namespace, Collections.singletonMap(name.toLowerCase(), value));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
index 0e40669cf870..d68b81490f6e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityThread;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.BadConfigException;
import android.provider.DeviceConfig.Properties;
import androidx.test.filters.SmallTest;
@@ -92,6 +93,16 @@ public class TestableDeviceConfigTest {
}
@Test
+ public void setProperties() throws BadConfigException {
+ String newKey = "key2";
+ String newValue = "value2";
+ DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey,
+ sValue).setString(newKey, newValue).build());
+ assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue);
+ assertThat(DeviceConfig.getProperty(sNamespace, newKey)).isEqualTo(newValue);
+ }
+
+ @Test
public void getProperties_empty() {
String newKey = "key2";
String newValue = "value2";
@@ -131,13 +142,12 @@ public class TestableDeviceConfigTest {
}
@Test
- public void testListener() throws InterruptedException {
+ public void testListener_setProperty() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
OnPropertiesChangedListener changeListener = (properties) -> {
assertThat(properties.getNamespace()).isEqualTo(sNamespace);
- assertThat(properties.getKeyset().size()).isEqualTo(1);
- assertThat(properties.getKeyset()).contains(sKey);
+ assertThat(properties.getKeyset()).containsExactly(sKey);
assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue);
assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
countDownLatch.countDown();
@@ -153,6 +163,32 @@ public class TestableDeviceConfigTest {
}
}
+ @Test
+ public void testListener_setProperties() throws BadConfigException, InterruptedException {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ String newKey = "key2";
+ String newValue = "value2";
+
+ OnPropertiesChangedListener changeListener = (properties) -> {
+ assertThat(properties.getNamespace()).isEqualTo(sNamespace);
+ assertThat(properties.getKeyset()).containsExactly(sKey, newKey);
+ assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue);
+ assertThat(properties.getString(newKey, "bogus_value")).isEqualTo(newValue);
+ assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
+ countDownLatch.countDown();
+ };
+ try {
+ DeviceConfig.addOnPropertiesChangedListener(sNamespace,
+ ActivityThread.currentApplication().getMainExecutor(), changeListener);
+ DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey,
+ sValue).setString(newKey, newValue).build());
+ assertThat(countDownLatch.await(
+ WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ } finally {
+ DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+ }
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java
new file mode 100644
index 000000000000..ea7a9a45facd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/LocalColorRepositoryTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.wallpaper;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+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 static org.mockito.MockitoAnnotations.initMocks;
+
+import static java.util.Arrays.asList;
+
+import android.app.ILocalWallpaperColorConsumer;
+import android.app.WallpaperColors;
+import android.graphics.RectF;
+import android.os.IBinder;
+
+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 android.util.ArraySet;
+import java.util.List;
+import java.util.function.Consumer;
+
+
+@RunWith(AndroidJUnit4.class)
+public class LocalColorRepositoryTest {
+ private LocalColorRepository mRepo = new LocalColorRepository();
+ @Mock
+ private IBinder mBinder1;
+ @Mock
+ private IBinder mBinder2;
+ @Mock
+ private ILocalWallpaperColorConsumer mCallback1;
+ @Mock
+ private ILocalWallpaperColorConsumer mCallback2;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ when(mCallback1.asBinder()).thenReturn(mBinder1);
+ when(mCallback2.asBinder()).thenReturn(mBinder2);
+ }
+
+ @Test
+ public void testDisplayAreas() {
+ RectF area1 = new RectF(1, 0, 0, 0);
+ RectF area2 = new RectF(2, 1, 1, 1);
+ ArraySet<RectF> expectedAreas = new ArraySet(asList(area1, area2));
+
+ mRepo.addAreas(mCallback1, asList(area1), 0);
+ mRepo.addAreas(mCallback2, asList(area2), 0);
+ mRepo.addAreas(mCallback1, asList(new RectF(3, 1, 1, 1)), 1);
+
+ assertEquals(expectedAreas, new ArraySet(mRepo.getAreasByDisplayId(0)));
+ assertEquals(new ArraySet(asList(new RectF(3, 1, 1, 1))),
+ new ArraySet(mRepo.getAreasByDisplayId(1)));
+ assertEquals(new ArraySet(), new ArraySet(mRepo.getAreasByDisplayId(2)));
+ }
+
+ @Test
+ public void testAddAndRemoveAreas() {
+ RectF area1 = new RectF(1, 0, 0, 0);
+ RectF area2 = new RectF(2, 1, 1, 1);
+
+ mRepo.addAreas(mCallback1, asList(area1), 0);
+ mRepo.addAreas(mCallback1, asList(area2), 0);
+ mRepo.addAreas(mCallback2, asList(area2), 1);
+
+ List<RectF> removed = mRepo.removeAreas(mCallback1, asList(area1), 0);
+ assertEquals(new ArraySet(asList(area1)), new ArraySet(removed));
+ // since we have another callback with a different area, we don't purge rid of any areas
+ removed = mRepo.removeAreas(mCallback1, asList(area2), 0);
+ assertEquals(new ArraySet(), new ArraySet(removed));
+ }
+
+ @Test
+ public void testAreaCallback() {
+ Consumer<ILocalWallpaperColorConsumer> consumer = mock(Consumer.class);
+ WallpaperColors colors = mock(WallpaperColors.class);
+ RectF area1 = new RectF(1, 0, 0, 0);
+ RectF area2 = new RectF(2, 1, 1, 1);
+
+ mRepo.addAreas(mCallback1, asList(area1), 0);
+ mRepo.addAreas(mCallback1, asList(area2), 0);
+ mRepo.addAreas(mCallback2, asList(area2), 0);
+
+ mRepo.forEachCallback(consumer, area1, 0);
+ Mockito.verify(consumer, times(1)).accept(eq(mCallback1));
+ Mockito.verify(consumer, times(0)).accept(eq(mCallback2));
+ mRepo.forEachCallback(consumer, area2, 0);
+ Mockito.verify(consumer, times(2)).accept(eq(mCallback1));
+ Mockito.verify(consumer, times(1)).accept(eq(mCallback2));
+ }
+
+ @Test
+ public void unregisterCallbackWhenNoAreas() {
+ RectF area1 = new RectF(1, 0, 0, 0);
+ RectF area2 = new RectF(2, 1, 1, 1);
+
+ assertFalse(mRepo.isCallbackAvailable(mCallback1));
+
+ mRepo.addAreas(mCallback1, asList(area1), 0);
+ mRepo.addAreas(mCallback1, asList(area2), 0);
+
+ mRepo.removeAreas(mCallback1, asList(area1, area2), 0);
+ assertFalse(mRepo.isCallbackAvailable(mCallback1));
+
+ mRepo.addAreas(mCallback1, asList(area1), 0);
+ assertTrue(mRepo.isCallbackAvailable(mCallback1));
+ }
+}
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 a49afc75c739..fe23c14a0b95 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -152,6 +152,7 @@ public class WallpaperManagerServiceTests {
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER,
PackageManager.PERMISSION_GRANTED);
+ doNothing().when(sContext).sendBroadcastAsUser(any(), any());
//Wallpaper components
sWallpaperService = mock(IWallpaperConnection.Stub.class);
@@ -188,7 +189,6 @@ public class WallpaperManagerServiceTests {
MockitoAnnotations.initMocks(this);
sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
- doNothing().when(sContext).sendBroadcastAsUser(any(), any());
final Display mockDisplay = mock(Display.class);
doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 339a5f916b4b..7020744d661c 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -110,6 +110,7 @@ android_test {
data: [
":JobTestApp",
+ ":StubTestApp",
],
java_resources: [
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index bcb2cf8fa0c3..68b84693f0ef 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -71,6 +71,7 @@
<uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/>
<uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"/>
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+ <uses-permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 5a0f1ee963a2..bb3eb81df6ed 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -16,6 +16,13 @@
<configuration description="Runs Frameworks Services Tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file" key="SimpleServiceTestApp3.apk"
+ value="/data/local/tmp/cts/content/SimpleServiceTestApp3.apk" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
@@ -28,6 +35,17 @@
<option name="test-file-name" value="SimpleServiceTestApp3.apk" />
</target_preparer>
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/servicestests" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/>
+ </target_preparer>
+
+ <!-- Load additional APKs onto device -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/>
+ </target_preparer>
+
<option name="test-tag" value="FrameworksServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.servicestests" />
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 d6c11a549dfa..e612d121b093 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -65,6 +65,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
import android.content.Context;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
index 80e81d6e7cb9..554f0a4265be 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterTest.java
@@ -29,6 +29,7 @@ import static com.android.server.accessibility.AccessibilityInputFilter.FLAG_FEA
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -176,7 +177,7 @@ public class AccessibilityInputFilterTest {
}
@Test
- public void testEventHandler_shouldChangeAfterOnDisplayChanged() {
+ public void testEventHandler_shouldIncreaseAndHaveCorrectOrderAfterOnDisplayAdded() {
prepareLooper();
// Check if there is only one mEventHandler when there is one default display.
@@ -184,13 +185,51 @@ public class AccessibilityInputFilterTest {
assertEquals(1, mEventHandler.size());
// Check if it has correct numbers of mEventHandler for corresponding displays.
- setDisplayCount(4);
- mA11yInputFilter.onDisplayChanged();
- assertEquals(4, mEventHandler.size());
+ setDisplayCount(2);
+ mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY));
+ assertEquals(2, mEventHandler.size());
+
+ EventStreamTransformation next = mEventHandler.get(SECOND_DISPLAY);
+ assertNotNull(next);
+
+ // Start from index 1 because KeyboardInterceptor only exists in EventHandler for
+ // DEFAULT_DISPLAY.
+ for (int i = 1; next != null; i++) {
+ assertEquals(next.getClass(), mExpectedEventHandlerTypes[i]);
+ next = next.getNext();
+ }
+ }
+
+ @Test
+ public void testEventHandler_shouldDecreaseAfterOnDisplayRemoved() {
+ prepareLooper();
setDisplayCount(2);
- mA11yInputFilter.onDisplayChanged();
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
assertEquals(2, mEventHandler.size());
+
+ // Check if it has correct numbers of mEventHandler for corresponding displays.
+ mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
+ assertEquals(1, mEventHandler.size());
+
+ EventStreamTransformation eventHandler = mEventHandler.get(SECOND_DISPLAY);
+ assertNull(eventHandler);
+ }
+
+ @Test
+ public void testEventHandler_shouldNoChangedInOtherDisplayAfterOnDisplayRemoved() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ EventStreamTransformation eventHandlerBeforeDisplayRemoved =
+ mEventHandler.get(DEFAULT_DISPLAY);
+
+ mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
+ EventStreamTransformation eventHandlerAfterDisplayRemoved =
+ mEventHandler.get(DEFAULT_DISPLAY);
+
+ assertEquals(eventHandlerBeforeDisplayRemoved, eventHandlerAfterDisplayRemoved);
}
@Test
@@ -240,7 +279,7 @@ public class AccessibilityInputFilterTest {
}
@Test
- public void testInputEvent_shouldClearEventsForAllEventHandlers() {
+ public void testInputEvent_shouldClearEventsForDisplayEventHandlers() {
prepareLooper();
mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
@@ -253,13 +292,71 @@ public class AccessibilityInputFilterTest {
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
assertEquals(2, mCaptor1.mEvents.size());
- // InputEvent with different input source should trigger clearEvents() for each
- // EventStreamTransformation in EventHandler.
+ // InputEvent with different input source to the same display should trigger
+ // clearEvents() for the EventHandler in this display.
send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_MOUSE));
assertEquals(1, mCaptor1.mEvents.size());
}
@Test
+ public void testInputEvent_shouldNotClearEventsForOtherDisplayEventHandlers() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ assertEquals(2, mEventHandler.size());
+
+ mCaptor1 = new EventCaptor();
+ mCaptor2 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+ mEventHandler.put(SECOND_DISPLAY, mCaptor2);
+
+ // InputEvent with different displayId should be dispatched to corresponding EventHandler.
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+
+ // InputEvent with different input source should not trigger clearEvents() for
+ // the EventHandler in the other display.
+ send(downEvent(SECOND_DISPLAY, InputDevice.SOURCE_MOUSE));
+ assertEquals(2, mCaptor1.mEvents.size());
+ }
+
+ @Test
+ public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayAdded() {
+ prepareLooper();
+
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ mCaptor1 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ assertEquals(2, mCaptor1.mEvents.size());
+
+ setDisplayCount(2);
+ mA11yInputFilter.onDisplayAdded(mDisplayList.get(SECOND_DISPLAY));
+ assertEquals(2, mCaptor1.mEvents.size());
+ }
+
+ @Test
+ public void testInputEvent_shouldNotClearEventsForOtherDisplayAfterOnDisplayRemoved() {
+ prepareLooper();
+
+ setDisplayCount(2);
+ mA11yInputFilter.setUserAndEnabledFeatures(0, mFeatures);
+ mCaptor1 = new EventCaptor();
+ mEventHandler.put(DEFAULT_DISPLAY, mCaptor1);
+
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ send(downEvent(DEFAULT_DISPLAY, InputDevice.SOURCE_TOUCHSCREEN));
+ assertEquals(2, mCaptor1.mEvents.size());
+
+ mA11yInputFilter.onDisplayRemoved(SECOND_DISPLAY);
+ assertEquals(2, mCaptor1.mEvents.size());
+ }
+
+ @Test
public void testEnabledFeatures_windowMagnificationMode_expectedMagnificationGestureHandler() {
prepareLooper();
doReturn(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW).when(
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 2a5bb18ae428..df975cda54a5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -40,7 +40,6 @@ import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.os.UserHandle;
import android.provider.Settings;
-import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -54,13 +53,16 @@ import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* APCT tests for {@link AccessibilityManagerService}.
*/
-public class AccessibilityManagerServiceTest extends AndroidTestCase {
+public class AccessibilityManagerServiceTest {
private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
private static final int ACTION_ID = 20;
private static final String LABEL = "label";
@@ -104,8 +106,8 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
private AccessibilityServiceConnection mAccessibilityServiceConnection;
private AccessibilityManagerService mA11yms;
- @Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
@@ -167,44 +169,48 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
}
@SmallTest
+ @Test
public void testRegisterSystemActionWithoutPermission() throws Exception {
doThrow(SecurityException.class).when(mMockSecurityPolicy)
.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
try {
mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
- fail();
+ Assert.fail();
} catch (SecurityException expected) {
}
verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
}
@SmallTest
+ @Test
public void testRegisterSystemAction() throws Exception {
mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
}
- @SmallTest
+ @Test
public void testUnregisterSystemActionWithoutPermission() throws Exception {
doThrow(SecurityException.class).when(mMockSecurityPolicy)
.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY);
try {
mA11yms.unregisterSystemAction(ACTION_ID);
- fail();
+ Assert.fail();
} catch (SecurityException expected) {
}
verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
}
@SmallTest
+ @Test
public void testUnregisterSystemAction() throws Exception {
mA11yms.unregisterSystemAction(ACTION_ID);
verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
}
@SmallTest
+ @Test
public void testOnSystemActionsChanged() throws Exception {
setupAccessibilityServiceConnection();
mA11yms.notifySystemActionsChangedLocked(mUserState);
@@ -213,6 +219,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
}
@SmallTest
+ @Test
public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() {
final AccessibilityUserState userState = mA11yms.mUserStates.get(
mA11yms.getCurrentUserIdLocked());
@@ -223,7 +230,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
mA11yms.onMagnificationTransitionEndedLocked(false);
- assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ Assert.assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
userState.getMagnificationModeLocked());
}
}
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 00daa5c89fba..432a500a5041 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
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 e4d51e4374a7..4afe0996e12a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -120,6 +120,7 @@ public class AccessibilityWindowManagerTest {
@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;
@@ -140,7 +141,8 @@ public class AccessibilityWindowManagerTest {
mMockWindowManagerInternal,
mMockA11yEventSender,
mMockA11ySecurityPolicy,
- mMockA11yUserManager);
+ 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);
@@ -834,6 +836,19 @@ public class AccessibilityWindowManagerTest {
assertNull(token);
}
+ @Test
+ public void onDisplayReparented_shouldRemoveObserver() throws RemoteException {
+ // Starts tracking window of second display.
+ startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+ assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+ // Notifies the second display is an embedded one of the default display.
+ final WindowsForAccessibilityCallback callbacks =
+ mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
+ callbacks.onDisplayReparented(SECONDARY_DISPLAY_ID);
+ // Makes sure the observer of the second display is removed.
+ assertFalse(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+ }
+
private void registerLeashedTokenAndWindowId() {
mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 78e651b7a3c1..c62cae5e9b6e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -56,11 +56,13 @@ public class KeyboardInterceptorTest {
private MessageCapturingHandler mHandler = new MessageCapturingHandler(
msg -> mInterceptor.handleMessage(msg));
@Mock AccessibilityManagerService mMockAms;
+ @Mock AccessibilityTraceManager mMockTraceManager;
@Mock WindowManagerPolicy mMockPolicy;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
mInterceptor = new KeyboardInterceptor(mMockAms, mMockPolicy, mHandler);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index d4908ee78a1d..59b69f9ffebf 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -122,6 +122,7 @@ public class MotionEventInjectorTest {
MotionEventInjector mMotionEventInjector;
IAccessibilityServiceClient mServiceInterface;
+ AccessibilityTraceManager mTrace;
List<GestureStep> mLineList = new ArrayList<>();
List<GestureStep> mClickList = new ArrayList<>();
List<GestureStep> mContinuedLineList1 = new ArrayList<>();
@@ -148,7 +149,8 @@ public class MotionEventInjectorTest {
return mMotionEventInjector.handleMessage(msg);
}
});
- mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
+ mTrace = mock(AccessibilityTraceManager.class);
+ mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler, mTrace);
mServiceInterface = mock(IAccessibilityServiceClient.class);
mLineList = createSimpleGestureFromPoints(0, 0, false, LINE_DURATION, LINE_START, LINE_END);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 8b6b7c235c44..1d6ed038b86d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -296,14 +296,6 @@ public class SystemActionPerformerTest {
}
@Test
- public void testToggleSplitScreen_legacy() {
- setupWithRealContext();
- mSystemActionPerformer.performSystemAction(
- AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN);
- verify(mMockStatusBarManagerInternal).toggleSplitScreen();
- }
-
- @Test
public void testScreenshot_requestsFromScreenshotHelper_legacy() {
setupWithMockContext();
mSystemActionPerformer.performSystemAction(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 160308762a58..4ce9ba031b25 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.app.UiAutomation;
import android.content.Context;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index 57ee7aa522c2..4a06611f397e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -36,6 +36,7 @@ import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_E
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
@@ -53,6 +54,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.utils.GestureLogParser;
import com.android.server.testutils.OffsettableClock;
@@ -107,6 +109,8 @@ public class TouchExplorerTest {
@Mock
private AccessibilityManagerService mMockAms;
+ @Mock
+ private AccessibilityTraceManager mMockTraceManager;
@Captor
private ArgumentCaptor<AccessibilityGestureEvent> mGestureCaptor;
@@ -143,6 +147,7 @@ public class TouchExplorerTest {
if (Looper.myLooper() == null) {
Looper.prepare();
}
+ when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
mContext = InstrumentationRegistry.getContext();
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
mCaptor = new EventCaptor();
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 502f64a1e50b..fe4fed9da468 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
@@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
@@ -93,6 +94,7 @@ public class FullScreenMagnificationControllerTest {
mock(FullScreenMagnificationController.ControllerContext.class);
final Context mMockContext = mock(Context.class);
final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
+ final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class);
final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
private final MagnificationAnimationCallback mAnimationCallback = mock(
MagnificationAnimationCallback.class);
@@ -113,9 +115,11 @@ public class FullScreenMagnificationControllerTest {
when(mMockContext.getMainLooper()).thenReturn(looper);
when(mMockControllerCtx.getContext()).thenReturn(mMockContext);
when(mMockControllerCtx.getAms()).thenReturn(mMockAms);
+ when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager);
when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager);
when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler);
when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L);
+ when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager);
initMockWindowManager();
mFullScreenMagnificationController = new FullScreenMagnificationController(
@@ -773,20 +777,20 @@ public class FullScreenMagnificationControllerTest {
}
@Test
- public void testRotation_resetsMagnification() {
+ public void testDisplaySizeChanged_resetsMagnification() {
for (int i = 0; i < DISPLAY_COUNT; i++) {
- rotation_resetsMagnification(i);
+ changeDisplaySize_resetsMagnification(i);
resetMockWindowManager();
}
}
- private void rotation_resetsMagnification(int displayId) {
+ private void changeDisplaySize_resetsMagnification(int displayId) {
register(displayId);
MagnificationCallbacks callbacks = getMagnificationCallbacks(displayId);
zoomIn2xToMiddle(displayId);
mMessageCapturingHandler.sendAllMessages();
assertTrue(mFullScreenMagnificationController.isMagnifying(displayId));
- callbacks.onRotationChanged(0);
+ callbacks.onDisplaySizeChanged();
mMessageCapturingHandler.sendAllMessages();
assertFalse(mFullScreenMagnificationController.isMagnifying(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 f881f04e5b07..b14c353397e2 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
@@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import com.android.server.testutils.OffsettableClock;
@@ -129,6 +130,10 @@ public class FullScreenMagnificationGestureHandlerTest {
MagnificationInfoChangedCallback mMagnificationInfoChangedCallback;
@Mock
WindowMagnificationPromptController mWindowMagnificationPromptController;
+ @Mock
+ AccessibilityManagerService mMockAccessibilityManagerService;
+ @Mock
+ AccessibilityTraceManager mMockTraceManager;
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
@@ -144,7 +149,9 @@ public class FullScreenMagnificationGestureHandlerTest {
mock(FullScreenMagnificationController.ControllerContext.class);
final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class);
when(mockController.getContext()).thenReturn(mContext);
- when(mockController.getAms()).thenReturn(mock(AccessibilityManagerService.class));
+ when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService);
+ when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager);
+ when(mockController.getTraceManager()).thenReturn(mMockTraceManager);
when(mockController.getWindowManager()).thenReturn(mockWindowManager);
when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper()));
when(mockController.newValueAnimator()).thenReturn(new ValueAnimator());
@@ -179,7 +186,7 @@ public class FullScreenMagnificationGestureHandlerTest {
private FullScreenMagnificationGestureHandler newInstance(boolean detectTripleTap,
boolean detectShortcutTrigger) {
FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler(
- mContext, mFullScreenMagnificationController, mMockCallback,
+ mContext, mFullScreenMagnificationController, mMockTraceManager, mMockCallback,
detectTripleTap, detectShortcutTrigger,
mWindowMagnificationPromptController, DISPLAY_0);
mHandler = new TestHandler(h.mDetectingState, mClock) {
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 b7f5f4da9d9d..e82adc8b403b 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
@@ -54,6 +54,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.AccessibilityTraceManager;
import org.junit.After;
import org.junit.Before;
@@ -84,6 +85,8 @@ public class MagnificationControllerTest {
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
@Mock
+ private AccessibilityTraceManager mTraceManager;
+ @Mock
private AccessibilityManagerService mService;
@Mock
private MagnificationController.TransitionCallBack mTransitionCallBack;
@@ -112,7 +115,7 @@ public class MagnificationControllerTest {
CURRENT_USER_ID);
mWindowMagnificationManager = Mockito.spy(
new WindowMagnificationManager(mContext, CURRENT_USER_ID,
- mock(WindowMagnificationManager.Callback.class)));
+ mock(WindowMagnificationManager.Callback.class), mTraceManager));
mMockConnection = new MockWindowMagnificationConnection(true);
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber(
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 514d16a0149c..ef6ed88011ef 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
@@ -31,6 +31,8 @@ import android.view.MotionEvent;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.AccessibilityTraceManager;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,6 +51,8 @@ public class MagnificationGestureHandlerTest {
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
@Mock
+ AccessibilityTraceManager mTraceManager;
+ @Mock
MagnificationGestureHandler.Callback mCallback;
@Before
@@ -57,6 +61,7 @@ public class MagnificationGestureHandlerTest {
mMgh = new TestMagnificationGestureHandler(DISPLAY_0,
/* detectTripleTap= */true,
/* detectShortcutTrigger= */true,
+ mTraceManager,
mCallback);
}
@@ -129,8 +134,9 @@ public class MagnificationGestureHandlerTest {
boolean mIsInternalMethodCalled = false;
TestMagnificationGestureHandler(int displayId, boolean detectTripleTap,
- boolean detectShortcutTrigger, @NonNull Callback callback) {
- super(displayId, detectTripleTap, detectShortcutTrigger, callback);
+ boolean detectShortcutTrigger, @NonNull AccessibilityTraceManager trace,
+ @NonNull Callback callback) {
+ super(displayId, detectTripleTap, detectShortcutTrigger, trace, callback);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
index c88bc3b2e15b..1638563e8242 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
@@ -29,6 +29,8 @@ import android.view.accessibility.IWindowMagnificationConnection;
import android.view.accessibility.IWindowMagnificationConnectionCallback;
import android.view.accessibility.MagnificationAnimationCallback;
+import com.android.server.accessibility.AccessibilityTraceManager;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -45,6 +47,8 @@ public class WindowMagnificationConnectionWrapperTest {
private IWindowMagnificationConnection mConnection;
@Mock
+ private AccessibilityTraceManager mTrace;
+ @Mock
private IWindowMagnificationConnectionCallback mCallback;
@Mock
private MagnificationAnimationCallback mAnimationCallback;
@@ -57,7 +61,7 @@ public class WindowMagnificationConnectionWrapperTest {
MockitoAnnotations.initMocks(this);
mMockWindowMagnificationConnection = new MockWindowMagnificationConnection();
mConnection = mMockWindowMagnificationConnection.getConnection();
- mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection);
+ mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection, mTrace);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
index b9498d641ed7..6a5aae672881 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java
@@ -35,6 +35,7 @@ import android.view.ViewConfiguration;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.EventStreamTransformation;
import com.android.server.accessibility.utils.TouchEventGenerator;
@@ -74,16 +75,18 @@ public class WindowMagnificationGestureHandlerTest {
private WindowMagnificationGestureHandler mWindowMagnificationGestureHandler;
@Mock
MagnificationGestureHandler.Callback mMockCallback;
+ @Mock
+ AccessibilityTraceManager mMockTrace;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mWindowMagnificationManager = new WindowMagnificationManager(mContext, 0,
- mock(WindowMagnificationManager.Callback.class));
+ mock(WindowMagnificationManager.Callback.class), mMockTrace);
mMockConnection = new MockWindowMagnificationConnection();
mWindowMagnificationGestureHandler = new WindowMagnificationGestureHandler(
- mContext, mWindowMagnificationManager, mMockCallback,
+ mContext, mWindowMagnificationManager, mMockTrace, mMockCallback,
/** detectTripleTap= */true, /** detectShortcutTrigger= */true, DISPLAY_0);
mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
mWindowMagnificationGestureHandler.setNext(strictMock(EventStreamTransformation.class));
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index a20272ab438d..af6d40f2fdf2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -52,6 +52,7 @@ import android.view.accessibility.MagnificationAnimationCallback;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.statusbar.StatusBarManagerInternal;
import org.junit.Before;
@@ -72,6 +73,8 @@ public class WindowMagnificationManagerTest {
@Mock
private Context mContext;
@Mock
+ private AccessibilityTraceManager mMockTrace;
+ @Mock
private StatusBarManagerInternal mMockStatusBarManagerInternal;
@Mock
private MagnificationAnimationCallback mAnimationCallback;
@@ -88,7 +91,7 @@ public class WindowMagnificationManagerTest {
mResolver = new MockContentResolver();
mMockConnection = new MockWindowMagnificationConnection();
mWindowMagnificationManager = new WindowMagnificationManager(mContext, CURRENT_USER_ID,
- mMockCallback);
+ mMockCallback, mMockTrace);
when(mContext.getContentResolver()).thenReturn(mResolver);
doAnswer((InvocationOnMock invocation) -> {
diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
index 4a67ec71fcaa..6faa7e7c89e7 100644
--- a/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BatteryExternalStatsWorkerTest.java
@@ -74,15 +74,20 @@ public class BatteryExternalStatsWorkerTest {
@Test
public void testTargetedEnergyConsumerQuerying() {
final int numCpuClusters = 4;
+ final int numDisplays = 5;
final int numOther = 3;
// Add some energy consumers used by BatteryExternalStatsWorker.
final IntArray tempAllIds = new IntArray();
- final int displayId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.DISPLAY, 0,
- "display");
- tempAllIds.add(displayId);
- mPowerStatsInternal.incrementEnergyConsumption(displayId, 12345);
+ final int[] displayIds = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayIds[i] = mPowerStatsInternal.addEnergyConsumer(
+ EnergyConsumerType.DISPLAY, i, "display" + i);
+ tempAllIds.add(displayIds[i]);
+ mPowerStatsInternal.incrementEnergyConsumption(displayIds[i], 12345 + i);
+ }
+ Arrays.sort(displayIds);
final int wifiId = mPowerStatsInternal.addEnergyConsumer(EnergyConsumerType.WIFI, 0,
"wifi");
@@ -130,9 +135,13 @@ public class BatteryExternalStatsWorkerTest {
final EnergyConsumerResult[] displayResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null);
- // Results should only have the display energy consumer
- assertEquals(1, displayResults.length);
- assertEquals(displayId, displayResults[0].id);
+ // Results should only have the cpu cluster energy consumers
+ final int[] receivedDisplayIds = new int[displayResults.length];
+ for (int i = 0; i < displayResults.length; i++) {
+ receivedDisplayIds[i] = displayResults[i].id;
+ }
+ Arrays.sort(receivedDisplayIds);
+ assertArrayEquals(displayIds, receivedDisplayIds);
final EnergyConsumerResult[] wifiResults =
mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null);
@@ -193,6 +202,7 @@ public class BatteryExternalStatsWorkerTest {
public class TestBatteryStatsImpl extends BatteryStatsImpl {
public TestBatteryStatsImpl(Context context) {
mPowerProfile = new PowerProfile(context, true /* forTest */);
+ initTimersAndCounters();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
index 8c87506295f3..a0cbcadee844 100644
--- a/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/MeasuredEnergySnapshotTest.java
@@ -16,8 +16,6 @@
package com.android.server.am;
-import static com.android.server.am.MeasuredEnergySnapshot.UNAVAILABLE;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -120,7 +118,7 @@ public final class MeasuredEnergySnapshotTest {
// results0
MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
- assertEquals(UNAVAILABLE, delta.displayChargeUC);
+ assertNull(delta.displayChargeUC);
assertNull(delta.otherTotalChargeUC);
assertNull(delta.otherUidChargesUC);
}
@@ -130,7 +128,7 @@ public final class MeasuredEnergySnapshotTest {
assertNotNull(delta);
long expectedChargeUC;
expectedChargeUC = calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1);
- assertEquals(expectedChargeUC, delta.displayChargeUC);
+ assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
assertNotNull(delta.otherTotalChargeUC);
@@ -149,14 +147,14 @@ public final class MeasuredEnergySnapshotTest {
delta = snapshot.updateAndGetDelta(RESULTS_2, VOLTAGE_2);
assertNotNull(delta);
expectedChargeUC = calculateChargeConsumedUC(24_000, VOLTAGE_1, 36_000, VOLTAGE_2);
- assertEquals(expectedChargeUC, delta.displayChargeUC);
+ assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
assertNull(delta.otherUidChargesUC);
assertNull(delta.otherTotalChargeUC);
// results3
delta = snapshot.updateAndGetDelta(RESULTS_3, VOLTAGE_3);
assertNotNull(delta);
- assertEquals(UNAVAILABLE, delta.displayChargeUC);
+ assertNull(delta.displayChargeUC);
assertNotNull(delta.otherTotalChargeUC);
@@ -183,7 +181,7 @@ public final class MeasuredEnergySnapshotTest {
delta = snapshot.updateAndGetDelta(RESULTS_4, VOLTAGE_4);
assertNotNull(delta);
expectedChargeUC = calculateChargeConsumedUC(36_000, VOLTAGE_2, 43_000, VOLTAGE_4);
- assertEquals(expectedChargeUC, delta.displayChargeUC);
+ assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
assertNotNull(delta.otherTotalChargeUC);
expectedChargeUC = calculateChargeConsumedUC(190_000, VOLTAGE_3, 290_000, VOLTAGE_4);
@@ -210,7 +208,7 @@ public final class MeasuredEnergySnapshotTest {
// results0
MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
- assertEquals(UNAVAILABLE, delta.displayChargeUC);
+ assertNull(delta.displayChargeUC);
assertNull(delta.otherTotalChargeUC);
assertNull(delta.otherUidChargesUC);
}
@@ -220,7 +218,7 @@ public final class MeasuredEnergySnapshotTest {
assertNotNull(delta);
final long expectedChargeUC =
calculateChargeConsumedUC(14_000, VOLTAGE_0, 24_000, VOLTAGE_1);
- assertEquals(expectedChargeUC, delta.displayChargeUC);
+ assertEquals(expectedChargeUC, delta.displayChargeUC[0]);
assertNull(delta.otherTotalChargeUC); // Although in the results, they're not in the idMap
assertNull(delta.otherUidChargesUC);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
index 10f4c05eb6d8..e6a8dea973c9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -26,7 +27,9 @@ import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
import android.os.SystemClock;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
@@ -42,6 +45,8 @@ import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Build/Install/Run:
@@ -69,6 +74,12 @@ public final class ServiceRestarterTest {
private static final int ACTION_STOPPKG = 8;
private static final int ACTION_ALL = ACTION_START | ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG;
+ private static final String LOCAL_APK_BASE_PATH = "/data/local/tmp/cts/content/";
+ private static final String TEST_PACKAGE3_APK = "SimpleServiceTestApp3.apk";
+ private static final String ACTION_SERVICE_WITH_DEP_PKG =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG";
+ private static final String EXTRA_TARGET_PACKAGE = "target_package";
+
private Context mContext;
private Instrumentation mInstrumentation;
private int mTestPackage1Uid;
@@ -199,6 +210,83 @@ public final class ServiceRestarterTest {
return res;
}
+ @Test
+ public void testServiceWithDepPkgStopped() throws Exception {
+ final CountDownLatch[] latchHolder = new CountDownLatch[1];
+ final ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ latchHolder[0].countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ latchHolder[0].countDown();
+ }
+ };
+
+ final long timeout = 5_000;
+ final long shortTimeout = 2_000;
+ final Intent intent = new Intent(ACTION_SERVICE_WITH_DEP_PKG);
+ final String testPkg = TEST_PACKAGE2_NAME;
+ final String libPkg = TEST_PACKAGE3_NAME;
+ final String apkPath = LOCAL_APK_BASE_PATH + TEST_PACKAGE3_APK;
+ final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+
+ intent.setComponent(ComponentName.unflattenFromString(testPkg + "/" + TEST_SERVICE_NAME));
+ intent.putExtra(EXTRA_TARGET_PACKAGE, libPkg);
+ try {
+ executeShellCmd("am service-restart-backoff disable " + testPkg);
+
+ latchHolder[0] = new CountDownLatch(1);
+ assertTrue("Unable to bind to test service in " + testPkg,
+ mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE));
+ assertTrue("Timed out to bind service in " + testPkg,
+ latchHolder[0].await(timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(shortTimeout);
+ assertTrue(libPkg + " should be a dependency package of " + testPkg,
+ isPackageDependency(testPkg, libPkg));
+
+ // Force-stop lib package, the test service should be restarted.
+ latchHolder[0] = new CountDownLatch(2);
+ am.forceStopPackage(libPkg);
+ assertTrue("Test service in didn't restart in " + testPkg,
+ latchHolder[0].await(timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(shortTimeout);
+
+ // Re-install the lib package, the test service should be restarted.
+ latchHolder[0] = new CountDownLatch(2);
+ assertTrue("Unable to install package " + apkPath, installPackage(apkPath));
+ assertTrue("Test service in didn't restart in " + testPkg,
+ latchHolder[0].await(timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(shortTimeout);
+
+ // Force-stop the service package, the test service should not be restarted.
+ latchHolder[0] = new CountDownLatch(2);
+ am.forceStopPackage(testPkg);
+ assertFalse("Test service should not be restarted in " + testPkg,
+ latchHolder[0].await(timeout * 2, TimeUnit.MILLISECONDS));
+ } finally {
+ executeShellCmd("am service-restart-backoff enable " + testPkg);
+ mContext.unbindService(conn);
+ am.forceStopPackage(testPkg);
+ }
+ }
+
+ private boolean isPackageDependency(String pkgName, String libPackage) throws Exception {
+ final String output = SystemUtil.runShellCommand("dumpsys activity processes " + pkgName);
+ final Matcher matcher = Pattern.compile("packageDependencies=\\{.*?\\b"
+ + libPackage + "\\b.*?\\}").matcher(output);
+ return matcher.find();
+ }
+
+ private boolean installPackage(String apkPath) throws Exception {
+ return executeShellCmd("pm install -t " + apkPath).equals("Success\n");
+ }
+
private void startServiceAndWait(String pkgName, MyUidImportanceListener uidListener,
long timeout) throws Exception {
final Intent intent = new Intent();
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 5c7a580a2593..1c49e6e64dd8 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -146,12 +146,15 @@ public final class AppHibernationServiceTest {
}
@Test
- public void testSetHibernatingForUser_packageIsHibernating() {
+ public void testSetHibernatingForUser_packageIsHibernating() throws Exception {
// WHEN we hibernate a package for a user
mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true);
// THEN the package is marked hibernating for the user
assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1));
+ verify(mIActivityManager).forceStopPackage(PACKAGE_NAME_1, USER_ID_1);
+ verify(mIPackageManager).deleteApplicationCacheFilesAsUser(
+ eq(PACKAGE_NAME_1), eq(USER_ID_1), any());
}
@Test
@@ -204,6 +207,7 @@ public final class AppHibernationServiceTest {
// THEN the package is marked hibernating for the user
assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1));
+ verify(mPackageManagerInternal).deleteOatArtifactsOfPackage(PACKAGE_NAME_1);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
index 0d475c00569e..91bf4d12a299 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -135,7 +135,8 @@ public class AppSearchImplPlatformTest {
"schema1",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
// "schema1" is platform hidden now and package visible to package1
assertThat(mVisibilityStore.isSchemaSearchableByCaller(
@@ -167,7 +168,8 @@ public class AppSearchImplPlatformTest {
"schema1",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
// Check that "schema1" still has the same visibility settings
SystemUtil.runWithShellPermissionIdentity(() -> assertThat(
@@ -241,7 +243,8 @@ public class AppSearchImplPlatformTest {
"schema1",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
// "schema1" is platform hidden now and package accessible
assertThat(mVisibilityStore.isSchemaSearchableByCaller(
@@ -269,7 +272,8 @@ public class AppSearchImplPlatformTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
// Check that "schema1" is no longer considered platform hidden or package accessible
assertThat(mVisibilityStore.isSchemaSearchableByCaller(
@@ -298,7 +302,8 @@ public class AppSearchImplPlatformTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
assertThat(mVisibilityStore.isSchemaSearchableByCaller(
"package",
@@ -333,7 +338,8 @@ public class AppSearchImplPlatformTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
assertThat(mVisibilityStore.isSchemaSearchableByCaller(
"package",
@@ -361,7 +367,8 @@ public class AppSearchImplPlatformTest {
/*schemasNotDisplayedBySystem=*/ Collections.singletonList("Schema"),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
assertThat(mVisibilityStore.isSchemaSearchableByCaller(
"package",
@@ -390,7 +397,8 @@ public class AppSearchImplPlatformTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
assertThat(mVisibilityStore
.isSchemaSearchableByCaller(
"package",
@@ -431,7 +439,8 @@ public class AppSearchImplPlatformTest {
"Schema",
ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))),
/*forceOverride=*/ false,
- /*schemaVersion=*/ 0);
+ /*schemaVersion=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
assertThat(mVisibilityStore
.isSchemaSearchableByCaller(
"package",
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index f40a5ad7bcb6..dd3b3ec08dbf 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -76,13 +76,14 @@ import java.util.Map;
import java.util.Set;
public class AppSearchImplTest {
- @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
- private AppSearchImpl mAppSearchImpl;
/**
* Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
*/
private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private AppSearchImpl mAppSearchImpl;
+
@Before
public void setUp() throws Exception {
mAppSearchImpl =
@@ -439,7 +440,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert a document and then remove it to generate garbage.
GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
@@ -499,7 +501,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert a valid doc
GenericDocument validDoc =
@@ -591,7 +594,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert a valid doc
appSearchImpl.putDocument(
@@ -626,7 +630,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert document
GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
@@ -660,7 +665,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"package",
"database2",
@@ -669,7 +675,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert documents
GenericDocument document1 =
@@ -714,7 +721,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert document
GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build();
@@ -756,7 +764,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -769,7 +778,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert package1 document
GenericDocument document =
@@ -812,7 +822,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -825,7 +836,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert package1 document
GenericDocument document =
@@ -889,7 +901,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -914,7 +927,8 @@ public class AppSearchImplTest {
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
- searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+ searchResultPage =
+ mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@@ -932,7 +946,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -962,14 +977,17 @@ public class AppSearchImplTest {
AppSearchException e =
assertThrows(
AppSearchException.class,
- () -> mAppSearchImpl.getNextPage("package2", nextPageToken));
+ () ->
+ mAppSearchImpl.getNextPage(
+ "package2", nextPageToken, /*statsBuilder=*/ null));
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
- searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+ searchResultPage =
+ mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@@ -987,7 +1005,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -1019,7 +1038,8 @@ public class AppSearchImplTest {
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
long nextPageToken = searchResultPage.getNextPageToken();
- searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+ searchResultPage =
+ mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@@ -1037,7 +1057,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -1074,14 +1095,17 @@ public class AppSearchImplTest {
AppSearchException e =
assertThrows(
AppSearchException.class,
- () -> mAppSearchImpl.getNextPage("package2", nextPageToken));
+ () ->
+ mAppSearchImpl.getNextPage(
+ "package2", nextPageToken, /*statsBuilder=*/ null));
assertThat(e)
.hasMessageThat()
.contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken);
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
- searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+ searchResultPage =
+ mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@@ -1099,7 +1123,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -1132,7 +1157,9 @@ public class AppSearchImplTest {
AppSearchException e =
assertThrows(
AppSearchException.class,
- () -> mAppSearchImpl.getNextPage("package1", nextPageToken));
+ () ->
+ mAppSearchImpl.getNextPage(
+ "package1", nextPageToken, /*statsBuilder=*/ null));
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
@@ -1152,7 +1179,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -1189,7 +1217,8 @@ public class AppSearchImplTest {
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
- searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+ searchResultPage =
+ mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@@ -1207,7 +1236,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -1247,7 +1277,9 @@ public class AppSearchImplTest {
AppSearchException e =
assertThrows(
AppSearchException.class,
- () -> mAppSearchImpl.getNextPage("package1", nextPageToken));
+ () ->
+ mAppSearchImpl.getNextPage(
+ "package1", nextPageToken, /*statsBuilder=*/ null));
assertThat(e)
.hasMessageThat()
.contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken);
@@ -1267,7 +1299,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two package1 documents
GenericDocument document1 =
@@ -1311,7 +1344,8 @@ public class AppSearchImplTest {
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR);
// Can continue getting next page for package1
- searchResultPage = mAppSearchImpl.getNextPage("package1", nextPageToken);
+ searchResultPage =
+ mAppSearchImpl.getNextPage("package1", nextPageToken, /*statsBuilder=*/ null);
assertThat(searchResultPage.getResults()).hasSize(1);
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
}
@@ -1355,7 +1389,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -1400,7 +1435,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Create incompatible schema
List<AppSearchSchema> newSchemas =
@@ -1416,7 +1452,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text");
assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email");
}
@@ -1439,7 +1476,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -1472,8 +1510,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
-
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Check the Document type has been deleted.
assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document");
@@ -1486,7 +1524,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Check Document schema is removed.
expectedProto =
@@ -1524,7 +1563,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"package",
"database2",
@@ -1533,7 +1573,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -1573,7 +1614,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ true,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Create expected schemaType list, database 1 should only contain Email but database 2
// remains in same.
@@ -1618,7 +1660,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert package document
GenericDocument document =
@@ -1680,7 +1723,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"packageB",
"database",
@@ -1689,7 +1733,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Verify these two packages is stored in AppSearch
SchemaProto expectedProto =
@@ -1735,7 +1780,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(expectedMapping);
@@ -1749,7 +1795,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(expectedMapping);
@@ -1763,7 +1810,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
assertThat(mAppSearchImpl.getPackageToDatabases())
.containsExactlyEntriesIn(expectedMapping);
}
@@ -1822,7 +1870,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two docs
GenericDocument document1 =
@@ -1973,7 +2022,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Since "package1" doesn't have a document, it get any space attributed to it.
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1");
@@ -1996,7 +2046,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert document for "package1"
GenericDocument document =
@@ -2012,7 +2063,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert two documents for "package2"
document = new GenericDocument.Builder<>("namespace", "id1", "type").build();
@@ -2061,7 +2113,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// "package2" doesn't exist yet, so it shouldn't have any storage size
StorageInfo storageInfo =
@@ -2084,7 +2137,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Since "package1", "database1" doesn't have a document, it get any space attributed to it.
StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1");
@@ -2106,7 +2160,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"package1",
"database2",
@@ -2115,7 +2170,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Add a document for "package1", "database1"
GenericDocument document =
@@ -2165,7 +2221,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
appSearchImpl.close();
@@ -2181,7 +2238,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0));
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null));
assertThrows(
IllegalStateException.class, () -> appSearchImpl.getSchema("package", "database"));
@@ -2225,7 +2283,9 @@ public class AppSearchImplTest {
assertThrows(
IllegalStateException.class,
- () -> appSearchImpl.getNextPage("package", /*nextPageToken=*/ 1L));
+ () ->
+ appSearchImpl.getNextPage(
+ "package", /*nextPageToken=*/ 1L, /*statsBuilder=*/ null));
assertThrows(
IllegalStateException.class,
@@ -2296,7 +2356,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Add a document and persist it.
GenericDocument document =
@@ -2343,7 +2404,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Add two documents and persist them.
GenericDocument document1 =
@@ -2423,7 +2485,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Add two documents and persist them.
GenericDocument document1 =
@@ -2511,7 +2574,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Add two documents
GenericDocument document1 =
@@ -2562,7 +2626,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert a document which is too large
GenericDocument document =
@@ -2636,7 +2701,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Index a document
mAppSearchImpl.putDocument(
@@ -2723,7 +2789,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Index 3 documents
mAppSearchImpl.putDocument(
@@ -2836,7 +2903,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"package1",
"database2",
@@ -2845,7 +2913,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"package2",
"database1",
@@ -2854,7 +2923,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
mAppSearchImpl.setSchema(
"package2",
"database2",
@@ -2863,7 +2933,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Index documents in package1/database1
mAppSearchImpl.putDocument(
@@ -3002,7 +3073,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Index 3 documents
mAppSearchImpl.putDocument(
@@ -3131,7 +3203,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Index a document
mAppSearchImpl.putDocument(
@@ -3210,7 +3283,8 @@ public class AppSearchImplTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Index a document
mAppSearchImpl.putDocument(
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
index 7c976876a731..2ab5fd554675 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -33,6 +33,7 @@ import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
import com.android.server.appsearch.external.localstorage.stats.SearchStats;
+import com.android.server.appsearch.external.localstorage.stats.SetSchemaStats;
import com.android.server.appsearch.icing.proto.DeleteStatsProto;
import com.android.server.appsearch.icing.proto.DocumentProto;
import com.android.server.appsearch.icing.proto.InitializeStatsProto;
@@ -41,6 +42,7 @@ import com.android.server.appsearch.icing.proto.PutDocumentStatsProto;
import com.android.server.appsearch.icing.proto.PutResultProto;
import com.android.server.appsearch.icing.proto.QueryStatsProto;
import com.android.server.appsearch.icing.proto.ScoringSpecProto;
+import com.android.server.appsearch.icing.proto.SetSchemaResultProto;
import com.android.server.appsearch.icing.proto.StatusProto;
import com.android.server.appsearch.icing.proto.TermMatchType;
@@ -57,14 +59,17 @@ import java.util.Collections;
import java.util.List;
public class AppSearchLoggerTest {
- @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
- private AppSearchImpl mAppSearchImpl;
- private TestLogger mLogger;
+ private static final String PACKAGE_NAME = "packageName";
+ private static final String DATABASE = "database";
/**
* Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class.
*/
private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true;
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ private AppSearchImpl mAppSearchImpl;
+ private TestLogger mLogger;
+
@Before
public void setUp() throws Exception {
mAppSearchImpl =
@@ -84,6 +89,7 @@ public class AppSearchLoggerTest {
@Nullable SearchStats mSearchStats;
@Nullable RemoveStats mRemoveStats;
@Nullable OptimizeStats mOptimizeStats;
+ @Nullable SetSchemaStats mSetSchemaStats;
@Override
public void logStats(@NonNull CallStats stats) {
@@ -114,6 +120,11 @@ public class AppSearchLoggerTest {
public void logStats(@NonNull OptimizeStats stats) {
mOptimizeStats = stats;
}
+
+ @Override
+ public void logStats(@NonNull SetSchemaStats stats) {
+ mSetSchemaStats = stats;
+ }
}
@Test
@@ -194,7 +205,7 @@ public class AppSearchLoggerTest {
.setExceededMaxTokenNum(nativeExceededMaxNumTokens)
.build())
.build();
- PutDocumentStats.Builder pBuilder = new PutDocumentStats.Builder("packageName", "database");
+ PutDocumentStats.Builder pBuilder = new PutDocumentStats.Builder(PACKAGE_NAME, DATABASE);
AppSearchLoggerHelper.copyNativeStats(nativePutDocumentStats, pBuilder);
@@ -248,8 +259,8 @@ public class AppSearchLoggerTest {
.setDocumentRetrievalLatencyMs(nativeDocumentRetrievingLatencyMillis)
.build();
SearchStats.Builder qBuilder =
- new SearchStats.Builder(SearchStats.VISIBILITY_SCOPE_LOCAL, "packageName")
- .setDatabase("database");
+ new SearchStats.Builder(SearchStats.VISIBILITY_SCOPE_LOCAL, PACKAGE_NAME)
+ .setDatabase(DATABASE);
AppSearchLoggerHelper.copyNativeStats(nativeQueryStats, qBuilder);
@@ -336,6 +347,35 @@ public class AppSearchLoggerTest {
.isEqualTo(nativeTimeSinceLastOptimizeMillis);
}
+ @Test
+ public void testAppSearchLoggerHelper_testCopyNativeStats_setSchema() {
+ ImmutableList<String> newSchemaTypeChangeList = ImmutableList.of("new1");
+ ImmutableList<String> deletedSchemaTypesList = ImmutableList.of("deleted1", "deleted2");
+ ImmutableList<String> compatibleTypesList = ImmutableList.of("compatible1", "compatible2");
+ ImmutableList<String> indexIncompatibleTypeChangeList = ImmutableList.of("index1");
+ ImmutableList<String> backwardsIncompatibleTypeChangeList = ImmutableList.of("backwards1");
+ SetSchemaResultProto setSchemaResultProto =
+ SetSchemaResultProto.newBuilder()
+ .addAllNewSchemaTypes(newSchemaTypeChangeList)
+ .addAllDeletedSchemaTypes(deletedSchemaTypesList)
+ .addAllFullyCompatibleChangedSchemaTypes(compatibleTypesList)
+ .addAllIndexIncompatibleChangedSchemaTypes(indexIncompatibleTypeChangeList)
+ .addAllIncompatibleSchemaTypes(backwardsIncompatibleTypeChangeList)
+ .build();
+ SetSchemaStats.Builder sBuilder = new SetSchemaStats.Builder(PACKAGE_NAME, DATABASE);
+
+ AppSearchLoggerHelper.copyNativeStats(setSchemaResultProto, sBuilder);
+
+ SetSchemaStats sStats = sBuilder.build();
+ assertThat(sStats.getNewTypeCount()).isEqualTo(newSchemaTypeChangeList.size());
+ assertThat(sStats.getDeletedTypeCount()).isEqualTo(deletedSchemaTypesList.size());
+ assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(compatibleTypesList.size());
+ assertThat(sStats.getIndexIncompatibleTypeChangeCount())
+ .isEqualTo(indexIncompatibleTypeChangeList.size());
+ assertThat(sStats.getBackwardsIncompatibleTypeChangeCount())
+ .isEqualTo(backwardsIncompatibleTypeChangeList.size());
+ }
+
//
// Testing actual logging
//
@@ -388,7 +428,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1").build();
GenericDocument doc2 = new GenericDocument.Builder<>("namespace", "id2", "Type1").build();
appSearchImpl.putDocument(testPackageName, testDatabase, doc1, mLogger);
@@ -439,7 +480,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
// Insert a valid doc
GenericDocument doc1 = new GenericDocument.Builder<>("namespace", "id1", "Type1").build();
@@ -495,7 +537,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "type")
@@ -542,7 +585,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument document =
new GenericDocument.Builder<>("namespace", "id", "type")
@@ -592,7 +636,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument document1 =
new GenericDocument.Builder<>("namespace", "id1", "type")
.setPropertyString("subject", "testPut example1")
@@ -661,7 +706,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
SearchSpec searchSpec =
new SearchSpec.Builder()
@@ -701,7 +747,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument document =
new GenericDocument.Builder<>(testNamespace, testId, "type").build();
mAppSearchImpl.putDocument(testPackageName, testDatabase, document, /*logger=*/ null);
@@ -735,7 +782,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument document =
new GenericDocument.Builder<>(testNamespace, testId, "type").build();
@@ -780,7 +828,8 @@ public class AppSearchLoggerTest {
/*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
/*schemasVisibleToPackages=*/ Collections.emptyMap(),
/*forceOverride=*/ false,
- /*version=*/ 0);
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
GenericDocument document1 =
new GenericDocument.Builder<>(testNamespace, "id1", "type").build();
GenericDocument document2 =
@@ -800,7 +849,58 @@ public class AppSearchLoggerTest {
assertThat(rStats.getDatabase()).isEqualTo(testDatabase);
assertThat(rStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_OK);
// delete by query
- assertThat(rStats.getDeleteType()).isEqualTo(DeleteStatsProto.DeleteType.Code.QUERY_VALUE);
+ assertThat(rStats.getDeleteType())
+ .isEqualTo(DeleteStatsProto.DeleteType.Code.DEPRECATED_QUERY_VALUE);
assertThat(rStats.getDeletedDocumentCount()).isEqualTo(2);
}
+
+ @Test
+ public void testLoggingStats_setSchema() throws Exception {
+ AppSearchSchema schema1 =
+ new AppSearchSchema.Builder("testSchema")
+ .addProperty(
+ new AppSearchSchema.StringPropertyConfig.Builder("subject")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED)
+ .setIndexingType(
+ AppSearchSchema.StringPropertyConfig
+ .INDEXING_TYPE_PREFIXES)
+ .setTokenizerType(
+ AppSearchSchema.StringPropertyConfig
+ .TOKENIZER_TYPE_PLAIN)
+ .build())
+ .build();
+ mAppSearchImpl.setSchema(
+ PACKAGE_NAME,
+ DATABASE,
+ Collections.singletonList(schema1),
+ /*visibilityStore=*/ null,
+ /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+ /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ null);
+
+ // create a backwards incompatible schema
+ SetSchemaStats.Builder sStatsBuilder = new SetSchemaStats.Builder(PACKAGE_NAME, DATABASE);
+ AppSearchSchema schema2 = new AppSearchSchema.Builder("testSchema").build();
+ mAppSearchImpl.setSchema(
+ PACKAGE_NAME,
+ DATABASE,
+ Collections.singletonList(schema2),
+ /*visibilityStore=*/ null,
+ /*schemasNotDisplayedBySystem=*/ Collections.emptyList(),
+ /*schemasVisibleToPackages=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /* setSchemaStatsBuilder= */ sStatsBuilder);
+
+ SetSchemaStats sStats = sStatsBuilder.build();
+ assertThat(sStats.getPackageName()).isEqualTo(PACKAGE_NAME);
+ assertThat(sStats.getDatabase()).isEqualTo(DATABASE);
+ assertThat(sStats.getNewTypeCount()).isEqualTo(0);
+ assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(0);
+ assertThat(sStats.getIndexIncompatibleTypeChangeCount()).isEqualTo(1);
+ assertThat(sStats.getBackwardsIncompatibleTypeChangeCount()).isEqualTo(1);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
index c1dc0e447c70..81aab416a9f9 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
@@ -264,17 +264,15 @@ public class AppSearchStatsTest {
.setMigratedDocumentCount(6)
.setSavedDocumentCount(7)
.build();
- int nativeLatencyMillis = 1;
- int newTypeCount = 2;
- int compatibleTypeChangeCount = 3;
- int indexIncompatibleTypeChangeCount = 4;
- int backwardsIncompatibleTypeChangeCount = 5;
+ int newTypeCount = 1;
+ int compatibleTypeChangeCount = 2;
+ int indexIncompatibleTypeChangeCount = 3;
+ int backwardsIncompatibleTypeChangeCount = 4;
SetSchemaStats sStats =
new SetSchemaStats.Builder(TEST_PACKAGE_NAME, TEST_DATA_BASE)
.setStatusCode(TEST_STATUS_CODE)
.setSchemaMigrationStats(schemaMigrationStats)
.setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
- .setNativeLatencyMillis(nativeLatencyMillis)
.setNewTypeCount(newTypeCount)
.setCompatibleTypeChangeCount(compatibleTypeChangeCount)
.setIndexIncompatibleTypeChangeCount(indexIncompatibleTypeChangeCount)
@@ -287,7 +285,6 @@ public class AppSearchStatsTest {
assertThat(sStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
assertThat(sStats.getSchemaMigrationStats()).isEqualTo(schemaMigrationStats);
assertThat(sStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
- assertThat(sStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis);
assertThat(sStats.getNewTypeCount()).isEqualTo(newTypeCount);
assertThat(sStats.getCompatibleTypeChangeCount()).isEqualTo(compatibleTypeChangeCount);
assertThat(sStats.getIndexIncompatibleTypeChangeCount())
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 b3f7587df612..b255a35c512e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -302,6 +302,65 @@ public class AuthSessionTest {
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
}
+ // TODO (b/208484275) : Enable these tests
+ // @Test
+ // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(false);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
+ // @Test
+ // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(true);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED,
+ // preAuthInfo.getCanAuthenticateResult());
+ // // Even though canAuth returns privacy enabled, we should still be able to authenticate.
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
+ // @Test
+ // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(true);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo =
+ // createPromptInfo(Authenticators.BIOMETRIC_STRONG
+ // | Authenticators. DEVICE_CREDENTIAL);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException {
final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
@@ -331,7 +390,8 @@ public class AuthSessionTest {
userId,
promptInfo,
TEST_PACKAGE,
- checkDevicePolicyManager);
+ checkDevicePolicyManager,
+ mContext);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
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 e3e3900c47e0..d192697827f6 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
@@ -143,8 +143,8 @@ public class BiometricSchedulerTest {
final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
- final BiometricPromptClientMonitor client1 =
- new BiometricPromptClientMonitor(mContext, mToken, lazyDaemon1, listener1);
+ final TestAuthenticationClient client1 =
+ new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1);
final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2);
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
@@ -180,8 +180,8 @@ public class BiometricSchedulerTest {
@Test
public void testCancelNotInvoked_whenOperationWaitingForCookie() {
final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class);
- final BiometricPromptClientMonitor client1 = new BiometricPromptClientMonitor(mContext,
- mToken, lazyDaemon1, mock(ClientMonitorCallbackConverter.class));
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
+ lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
// Schedule a BiometricPrompt authentication request
@@ -195,6 +195,8 @@ public class BiometricSchedulerTest {
// should go back to idle, since in this case the framework has not even requested the HAL
// to authenticate yet.
mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
+ assertTrue(client1.isAlreadyDone());
+ assertTrue(client1.mDestroyed);
assertNull(mScheduler.mCurrentOperation);
}
@@ -316,6 +318,10 @@ public class BiometricSchedulerTest {
eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
eq(0) /* vendorCode */);
assertNull(mScheduler.getCurrentClient());
+ assertTrue(client1.isAlreadyDone());
+ assertTrue(client1.mDestroyed);
+ assertTrue(client2.isAlreadyDone());
+ assertTrue(client2.mDestroyed);
}
@Test
@@ -465,39 +471,9 @@ public class BiometricSchedulerTest {
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
- private static class BiometricPromptClientMonitor extends AuthenticationClient<Object> {
-
- public BiometricPromptClientMonitor(@NonNull Context context, @NonNull IBinder token,
- @NonNull LazyDaemon<Object> lazyDaemon, ClientMonitorCallbackConverter listener) {
- super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
- false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
- TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
- 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
- false /* isKeyguard */, true /* shouldVibrate */,
- false /* isKeyguardBypassEnabled */);
- }
-
- @Override
- protected void stopHalOperation() {
- }
-
- @Override
- protected void startHalOperation() {
- }
-
- @Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {
-
- }
-
- @Override
- public boolean wasUserDetected() {
- return false;
- }
- }
-
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
int mNumCancels = 0;
+ boolean mDestroyed = false;
public TestAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -530,6 +506,13 @@ public class BiometricSchedulerTest {
return false;
}
+ @Override
+ public void destroy() {
+ mDestroyed = true;
+ super.destroy();
+ }
+
+ @Override
public void cancel() {
mNumCancels++;
super.cancel();
@@ -595,6 +578,7 @@ public class BiometricSchedulerTest {
@Override
public void destroy() {
+ super.destroy();
mDestroyed = true;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
new file mode 100644
index 000000000000..dc39b6d573db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.biometrics.sensors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class SensorOverlaysTest {
+
+ private static final int SENSOR_ID = 11;
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock private IUdfpsOverlayController mUdfpsOverlayController;
+ @Mock private ISidefpsController mSidefpsController;
+ @Mock private AcquisitionClient<?> mAcquisitionClient;
+
+ @Test
+ public void noopWhenBothNull() {
+ final SensorOverlays useless = new SensorOverlays(null, null);
+ useless.show(SENSOR_ID, 2, null);
+ useless.hide(SENSOR_ID);
+ }
+
+ @Test
+ public void testProvidesUdfps() {
+ final List<IUdfpsOverlayController> udfps = new ArrayList<>();
+ SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
+
+ sensorOverlays.ifUdfps(udfps::add);
+ assertThat(udfps).isEmpty();
+
+ sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
+ sensorOverlays.ifUdfps(udfps::add);
+ assertThat(udfps).containsExactly(mUdfpsOverlayController);
+ }
+
+ @Test
+ public void testShow() throws Exception {
+ testShow(mUdfpsOverlayController, mSidefpsController);
+ }
+
+ @Test
+ public void testShowUdfps() throws Exception {
+ testShow(mUdfpsOverlayController, null);
+ }
+
+ @Test
+ public void testShowSidefps() throws Exception {
+ testShow(null, mSidefpsController);
+ }
+
+ private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
+ throws Exception {
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+ final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
+ sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
+
+ if (udfps != null) {
+ verify(mUdfpsOverlayController).showUdfpsOverlay(eq(SENSOR_ID), eq(reason), any());
+ }
+ if (sidefps != null) {
+ verify(mSidefpsController).show(eq(SENSOR_ID), eq(reason));
+ }
+ }
+
+ @Test
+ public void testHide() throws Exception {
+ testHide(mUdfpsOverlayController, mSidefpsController);
+ }
+
+ @Test
+ public void testHideUdfps() throws Exception {
+ testHide(mUdfpsOverlayController, null);
+ }
+
+ @Test
+ public void testHideSidefps() throws Exception {
+ testHide(null, mSidefpsController);
+ }
+
+ private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
+ throws Exception {
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+ sensorOverlays.hide(SENSOR_ID);
+
+ if (udfps != null) {
+ verify(mUdfpsOverlayController).hideUdfpsOverlay(eq(SENSOR_ID));
+ }
+ if (sidefps != null) {
+ verify(mSidefpsController).hide(eq(SENSOR_ID));
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 0cd6d86a3ec9..0ac00aafbf6c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -19,13 +19,17 @@ package com.android.server.biometrics.sensors.face.aidl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
import android.hardware.biometrics.face.SensorProps;
+import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -33,7 +37,6 @@ import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -55,6 +58,8 @@ public class FaceProviderTest {
private Context mContext;
@Mock
private UserManager mUserManager;
+ @Mock
+ private IFace mDaemon;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -65,11 +70,12 @@ public class FaceProviderTest {
}
@Before
- public void setUp() {
+ public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+ when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -78,11 +84,11 @@ public class FaceProviderTest {
sensor2.commonProps = new CommonProps();
sensor2.commonProps.sensorId = 1;
- mSensorProps = new SensorProps[] {sensor1, sensor2};
+ mSensorProps = new SensorProps[]{sensor1, sensor2};
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG,
+ mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
mLockoutResetDispatcher);
}
@@ -127,17 +133,20 @@ public class FaceProviderTest {
}
private static class TestableFaceProvider extends FaceProvider {
- public TestableFaceProvider(@NonNull Context context,
+ private final IFace mDaemon;
+
+ TestableFaceProvider(@NonNull IFace daemon,
+ @NonNull Context context,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
super(context, props, halInstanceName, lockoutResetDispatcher);
+ mDaemon = daemon;
}
@Override
synchronized IFace getHalInstance() {
- return mock(IFace.class);
+ return mDaemon;
}
}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index b51918e24b13..73f1516562bc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -19,14 +19,20 @@ package com.android.server.biometrics.sensors.fingerprint.aidl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.SensorLocation;
import android.hardware.biometrics.fingerprint.SensorProps;
+import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -56,8 +62,12 @@ public class FingerprintProviderTest {
@Mock
private Context mContext;
@Mock
+ private Resources mResources;
+ @Mock
private UserManager mUserManager;
@Mock
+ private IFingerprint mDaemon;
+ @Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
@Mock
private FingerprintStateCallback mFingerprintStateCallback;
@@ -71,27 +81,31 @@ public class FingerprintProviderTest {
}
@Before
- public void setUp() {
+ public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.obtainTypedArray(anyInt())).thenReturn(mock(TypedArray.class));
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+ when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
- sensor1.sensorLocations = new SensorLocation[] {new SensorLocation()};
+ sensor1.sensorLocations = new SensorLocation[]{new SensorLocation()};
final SensorProps sensor2 = new SensorProps();
sensor2.commonProps = new CommonProps();
sensor2.commonProps.sensorId = 1;
- sensor2.sensorLocations = new SensorLocation[] {new SensorLocation()};
+ sensor2.sensorLocations = new SensorLocation[]{new SensorLocation()};
- mSensorProps = new SensorProps[] {sensor1, sensor2};
+ mSensorProps = new SensorProps[]{sensor1, sensor2};
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback,
- mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext,
+ mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
}
@SuppressWarnings("rawtypes")
@@ -135,7 +149,10 @@ public class FingerprintProviderTest {
}
private static class TestableFingerprintProvider extends FingerprintProvider {
- public TestableFingerprintProvider(@NonNull Context context,
+ private final IFingerprint mDaemon;
+
+ TestableFingerprintProvider(@NonNull IFingerprint daemon,
+ @NonNull Context context,
@NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@@ -143,11 +160,12 @@ public class FingerprintProviderTest {
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
gestureAvailabilityDispatcher);
+ mDaemon = daemon;
}
@Override
synchronized IFingerprint getHalInstance() {
- return mock(IFingerprint.class);
+ return mDaemon;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
new file mode 100644
index 000000000000..ea746d1f4fd3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/camera/CameraServiceProxyTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.camera;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.InstrumentationRegistry;
+
+import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
+import android.view.Display;
+import android.view.Surface;
+
+import java.util.HashMap;
+
+@RunWith(JUnit4.class)
+public class CameraServiceProxyTest {
+
+ @Test
+ public void testGetCropRotateScale() {
+
+ Context ctx = InstrumentationRegistry.getContext();
+
+ // Check resizeability and SDK
+ CameraServiceProxy.TaskInfo taskInfo = new CameraServiceProxy.TaskInfo();
+ taskInfo.isResizeable = true;
+ taskInfo.displayId = Display.DEFAULT_DISPLAY;
+ taskInfo.isFixedOrientationLandscape = false;
+ taskInfo.isFixedOrientationPortrait = true;
+ // Resizeable apps should be ignored
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90 , CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/false)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ // Resizeable apps will be considered in case the ignore flag is set
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ taskInfo.isResizeable = false;
+ // Non-resizeable apps should be considered
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/false)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ // The ignore flag for non-resizeable should have no effect
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ // Non-fixed orientation should be ignored
+ taskInfo.isFixedOrientationLandscape = false;
+ taskInfo.isFixedOrientationPortrait = false;
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ Surface.ROTATION_90, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(
+ CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ // Check rotation and lens facing combinations
+ HashMap<Integer, Integer> backFacingMap = new HashMap<Integer, Integer>() {{
+ put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
+ put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
+ }};
+ taskInfo.isFixedOrientationPortrait = true;
+ backFacingMap.forEach((key, value) -> {
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ key, CameraCharacteristics.LENS_FACING_BACK,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
+ });
+ HashMap<Integer, Integer> frontFacingMap = new HashMap<Integer, Integer>() {{
+ put(Surface.ROTATION_0, CameraMetadata.SCALER_ROTATE_AND_CROP_NONE);
+ put(Surface.ROTATION_90, CameraMetadata.SCALER_ROTATE_AND_CROP_270);
+ put(Surface.ROTATION_270, CameraMetadata.SCALER_ROTATE_AND_CROP_90);
+ put(Surface.ROTATION_180, CameraMetadata.SCALER_ROTATE_AND_CROP_180);
+ }};
+ frontFacingMap.forEach((key, value) -> {
+ assertThat(CameraServiceProxy.getCropRotateScale(ctx, ctx.getPackageName(), taskInfo,
+ key, CameraCharacteristics.LENS_FACING_FRONT,
+ /*ignoreResizableAndSdkCheck*/true)).isEqualTo(value);
+ });
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/camera/OWNERS b/services/tests/servicestests/src/com/android/server/camera/OWNERS
new file mode 100644
index 000000000000..f48a95c5b3a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/camera/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/av:/camera/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 0248b9b88788..d926dcba54a3 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -265,9 +265,10 @@ public class CompatConfigTest {
}
@Test
- public void testInstallerCanSetOverrides() throws Exception {
+ public void testInstallerCanAddOverrides() throws Exception {
final long disabledChangeId1 = 1234L;
final long disabledChangeId2 = 1235L;
+ final long unknownChangeId = 1236L;
// We make disabledChangeId2 non-overridable to make sure it is ignored.
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledOverridableChangeWithId(disabledChangeId1)
@@ -284,19 +285,25 @@ public class CompatConfigTest {
// Force the validator to prevent overriding non-overridable changes by using a user build.
when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+ Map<Long, PackageOverride> overrides = new HashMap<>();
+ overrides.put(disabledChangeId1, new PackageOverride.Builder()
+ .setMaxVersionCode(99L)
+ .setEnabled(true)
+ .build());
+ // Adding an unknown change ID to make sure it's skipped if skipUnknownChangeIds is true.
+ overrides.put(unknownChangeId, new PackageOverride.Builder().setEnabled(false).build());
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
- CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(
- Collections.singletonMap(disabledChangeId1,
- new PackageOverride.Builder()
- .setMaxVersionCode(99L)
- .setEnabled(true)
- .build()));
-
- compatConfig.addOverrides(config, "com.some.package");
+ compatConfig.addPackageOverrides(config, "com.some.package", /* skipUnknownChangeIds */
+ true);
assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue();
assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isFalse();
+ // Making sure the unknown change ID is still unknown and isChangeEnabled returns true.
+ assertThat(compatConfig.isKnownChangeId(unknownChangeId)).isFalse();
+ assertThat(compatConfig.isChangeEnabled(unknownChangeId, applicationInfo)).isTrue();
}
+
@Test
public void testPreventInstallerSetNonOverridable() throws Exception {
final long disabledChangeId1 = 1234L;
@@ -326,7 +333,8 @@ public class CompatConfigTest {
CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
assertThrows(SecurityException.class,
- () -> compatConfig.addOverrides(config, "com.some.package")
+ () -> compatConfig.addPackageOverrides(config, "com.some.package",
+ /* skipUnknownChangeIds */ true)
);
assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isTrue();
assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isFalse();
@@ -334,6 +342,38 @@ public class CompatConfigTest {
}
@Test
+ public void testCanAddOverridesForUnknownChangeIdOnDebugBuild() throws Exception {
+ final long disabledChangeId = 1234L;
+ final long unknownChangeId = 1235L;
+ // We make disabledChangeId2 non-overridable to make sure it is ignored.
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(disabledChangeId)
+ .build();
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package")
+ .build();
+ PackageManager packageManager = mock(PackageManager.class);
+ when(mContext.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(applicationInfo);
+
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+ Map<Long, PackageOverride> overrides = new HashMap<>();
+ overrides.put(disabledChangeId, new PackageOverride.Builder().setEnabled(true).build());
+ // Adding an unknown change ID to make sure it isn't skipped if skipUnknownChangeIds is
+ // false.
+ overrides.put(unknownChangeId, new PackageOverride.Builder().setEnabled(false).build());
+ CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides);
+
+ compatConfig.addPackageOverrides(config, "com.some.package", /* skipUnknownChangeIds */
+ false);
+ assertThat(compatConfig.isChangeEnabled(disabledChangeId, applicationInfo)).isTrue();
+ // Making sure the unknown change ID is now known and has an override.
+ assertThat(compatConfig.isKnownChangeId(unknownChangeId)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(unknownChangeId, applicationInfo)).isFalse();
+ }
+
+ @Test
public void testApplyDeferredOverridesAfterInstallingApp() throws Exception {
ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
.withPackageName("com.notinstalled.foo")
@@ -377,7 +417,8 @@ public class CompatConfigTest {
.setMaxVersionCode(99L)
.setEnabled(true)
.build()));
- compatConfig.addOverrides(config, "com.installed.foo");
+ compatConfig.addPackageOverrides(config, "com.installed.foo", /* skipUnknownChangeIds */
+ true);
assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
// Add override that does include the installed app version
@@ -388,7 +429,8 @@ public class CompatConfigTest {
.setMaxVersionCode(100L)
.setEnabled(true)
.build()));
- compatConfig.addOverrides(config, "com.installed.foo");
+ compatConfig.addPackageOverrides(config, "com.installed.foo", /* skipUnknownChangeIds */
+ true);
assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
}
@@ -411,7 +453,8 @@ public class CompatConfigTest {
.setMaxVersionCode(99L)
.setEnabled(true)
.build()));
- compatConfig.addOverrides(config, "com.notinstalled.foo");
+ compatConfig.addPackageOverrides(config, "com.notinstalled.foo", /* skipUnknownChangeIds */
+ true);
assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
// Pretend the app is now installed.
@@ -557,6 +600,7 @@ public class CompatConfigTest {
final long disabledChangeId1 = 1234L;
final long disabledChangeId2 = 1235L;
final long enabledChangeId = 1236L;
+ final long unknownChangeId = 1237L;
// We make disabledChangeId2 non-overridable to make sure it is ignored.
CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
.addDisabledOverridableChangeWithId(disabledChangeId1)
@@ -583,6 +627,8 @@ public class CompatConfigTest {
Set<Long> overridesToRemove = new HashSet<>();
overridesToRemove.add(disabledChangeId1);
overridesToRemove.add(enabledChangeId);
+ // Adding an unknown change ID to make sure it's skipped.
+ overridesToRemove.add(unknownChangeId);
CompatibilityOverridesToRemoveConfig config = new CompatibilityOverridesToRemoveConfig(
overridesToRemove);
@@ -590,6 +636,8 @@ public class CompatConfigTest {
assertThat(compatConfig.isChangeEnabled(disabledChangeId1, applicationInfo)).isFalse();
assertThat(compatConfig.isChangeEnabled(disabledChangeId2, applicationInfo)).isTrue();
assertThat(compatConfig.isChangeEnabled(enabledChangeId, applicationInfo)).isTrue();
+ // Making sure the unknown change ID is still unknown.
+ assertThat(compatConfig.isKnownChangeId(unknownChangeId)).isFalse();
}
@Test
@@ -797,18 +845,18 @@ public class CompatConfigTest {
.build());
when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt()))
.thenThrow(new NameNotFoundException());
- compatConfig.addOverrides(
+ compatConfig.addPackageOverrides(
new CompatibilityOverrideConfig(
Collections.singletonMap(
1L,
new PackageOverride.Builder().setEnabled(true).build())),
- "foo.bar");
- compatConfig.addOverrides(
+ "foo.bar", /* skipUnknownChangeIds */ true);
+ compatConfig.addPackageOverrides(
new CompatibilityOverrideConfig(
Collections.singletonMap(
2L,
new PackageOverride.Builder().setEnabled(false).build())),
- "bar.baz");
+ "bar.baz", /* skipUnknownChangeIds */ true);
assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<overrides>\n"
@@ -847,12 +895,12 @@ public class CompatConfigTest {
compatConfig.forceNonDebuggableFinalForTest(true);
compatConfig.initOverrides(overridesFile, new File(""));
- compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L,
- new PackageOverride.Builder()
+ compatConfig.addPackageOverrides(new CompatibilityOverrideConfig(
+ Collections.singletonMap(1L, new PackageOverride.Builder()
.setMinVersionCode(99L)
.setMaxVersionCode(101L)
.setEnabled(true)
- .build())), "foo.bar");
+ .build())), "foo.bar", /* skipUnknownChangeIds */ true);
assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<overrides>\n"
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 3ac30d0258a5..97fb399c63ef 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -51,6 +51,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -101,6 +102,7 @@ import android.graphics.Color;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.Uri;
+import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Process;
@@ -184,6 +186,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
public DevicePolicyManager parentDpm;
public DevicePolicyManagerServiceTestable dpms;
+ private boolean mIsAutomotive;
+
/*
* The CA cert below is the content of cacert.pem as generated by:
*
@@ -266,6 +270,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
setUpUserManager();
when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
+
+ mIsAutomotive = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -2096,9 +2103,12 @@ public class DevicePolicyManagerTest extends DpmTestBase {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
- setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID, null,
+ Build.VERSION_CODES.Q);
dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
boolean originalCameraDisabled = dpm.getCameraDisabled(admin1);
assertExpectException(SecurityException.class, /* messageRegex= */ null,
() -> dpm.setCameraDisabled(admin1, true));
@@ -2117,10 +2127,13 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertThat(dpm.getPasswordExpirationTimeout(admin1))
.isEqualTo(originalPasswordExpirationTimeout);
- int originalPasswordQuality = dpm.getPasswordQuality(admin1);
- assertExpectException(SecurityException.class, /* messageRegex= */ null,
- () -> dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC));
- assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality);
+ if (isDeprecatedPasswordApisSupported()) {
+ int originalPasswordQuality = dpm.getPasswordQuality(admin1);
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setPasswordQuality(admin1,
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC));
+ assertThat(dpm.getPasswordQuality(admin1)).isEqualTo(originalPasswordQuality);
+ }
}
@Test
@@ -2665,8 +2678,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
// Test 1. Caller doesn't have DO or DA.
- assertExpectException(SecurityException.class, /* messageRegex= */ "No active admin",
- () -> dpm.getWifiMacAddress(admin1));
+ assertExpectException(SecurityException.class, /* messageRegex= */
+ "does not exist or is not owned by uid", () -> dpm.getWifiMacAddress(admin1));
// DO needs to be an DA.
dpm.setActiveAdmin(admin1, /* replace =*/ false);
@@ -5231,6 +5244,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testIsActivePasswordSufficient() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
mContext.packageName = admin1.getPackageName();
setupDeviceOwner();
@@ -5283,6 +5298,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testIsActivePasswordSufficient_noLockScreen() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
// If there is no lock screen, the password is considered empty no matter what, because
// it provides no security.
when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false);
@@ -5363,6 +5380,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testGetAggregatedPasswordMetrics_IgnoreProfileRequirement()
throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
@@ -5392,6 +5411,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testCanSetPasswordRequirementOnParentPreS() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
@@ -5407,6 +5428,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testCannotSetPasswordRequirementOnParent() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
@@ -5427,6 +5450,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileQualityRequirementMet()
throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
// Create work profile with empty separate challenge
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
@@ -5450,6 +5475,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void isActivePasswordSufficient_SeparateWorkChallenge_ProfileComplexityRequirementMet()
throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
// Create work profile with empty separate challenge
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
@@ -5473,6 +5500,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void isActivePasswordSufficient_SeparateWorkChallenge_ParentQualityRequirementMet()
throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
// Create work profile with empty separate challenge
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
@@ -5519,6 +5548,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void isActivePasswordSufficient_UnifiedWorkChallenge_ProfileQualityRequirementMet()
throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
// Create work profile with unified challenge
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
@@ -5565,6 +5596,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void isActivePasswordSufficient_UnifiedWorkChallenge_ParentQualityRequirementMet()
throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
// Create work profile with unified challenge
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
@@ -5625,6 +5658,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testPasswordQualityAppliesToParentPreS() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
@@ -7285,6 +7320,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnDO() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
// DO must be able to set it.
@@ -7300,6 +7337,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_UnauthorizedCallersOnPO() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
setupProfileOwner();
// PO must be able to set it.
@@ -7314,6 +7353,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_validValuesOnly() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
setupProfileOwner();
@@ -7335,6 +7376,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_setAndGet() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
setupProfileOwner();
@@ -7348,6 +7391,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexityOnParent_setAndGet() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = 15;
final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436);
@@ -7366,6 +7411,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_isSufficient() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
mContext.packageName = admin1.getPackageName();
setupDeviceOwner();
@@ -7395,6 +7442,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_resetBySettingQuality() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
setupProfileOwner();
@@ -7407,6 +7456,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexity_overridesQuality() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
setupProfileOwner();
@@ -7421,6 +7472,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetRequiredPasswordComplexityFailsWithQualityOnParent() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
@@ -7435,6 +7488,8 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
public void testSetQualityOnParentFailsWithComplexityOnProfile() throws Exception {
+ assumeDeprecatedPasswordApisSupported();
+
final int managedProfileUserId = CALLER_USER_HANDLE;
final int managedProfileAdminUid =
UserHandle.getUid(managedProfileUserId, DpmMockContext.SYSTEM_UID);
@@ -8015,4 +8070,13 @@ public class DevicePolicyManagerTest extends DpmTestBase {
when(mContext.getResources().getStringArray(R.array.vendor_policy_exempt_apps))
.thenReturn(new String[0]);
}
+
+ private boolean isDeprecatedPasswordApisSupported() {
+ return !mIsAutomotive;
+ }
+
+ private void assumeDeprecatedPasswordApisSupported() {
+ assumeTrue("device doesn't support deprecated password APIs",
+ isDeprecatedPasswordApisSupported());
+ }
}
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 ff8fbda6c83e..ee0723b8b471 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -18,6 +18,8 @@ package com.android.server.devicestate;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
+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;
@@ -33,8 +35,12 @@ import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowProcessController;
+
import junit.framework.Assert;
import org.junit.Before;
@@ -55,10 +61,15 @@ import javax.annotation.Nullable;
@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DeviceStateManagerServiceTest {
- private static final DeviceState DEFAULT_DEVICE_STATE = new DeviceState(0, "DEFAULT");
- private static final DeviceState OTHER_DEVICE_STATE = new DeviceState(1, "OTHER");
+ private static final DeviceState DEFAULT_DEVICE_STATE =
+ new DeviceState(0, "DEFAULT", 0 /* flags */);
+ private static final DeviceState OTHER_DEVICE_STATE =
+ new DeviceState(1, "OTHER", 0 /* flags */);
// A device state that is not reported as being supported for the default test provider.
- private static final DeviceState UNSUPPORTED_DEVICE_STATE = new DeviceState(255, "UNSUPPORTED");
+ private static final DeviceState UNSUPPORTED_DEVICE_STATE =
+ new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
+
+ private static final int FAKE_PROCESS_ID = 100;
private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider;
@@ -69,6 +80,25 @@ public final class DeviceStateManagerServiceTest {
mProvider = new TestDeviceStateProvider();
mPolicy = new TestDeviceStatePolicy(mProvider);
mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
+
+ // Necessary to allow us to check for top app process id in tests
+ mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
+ WindowProcessController windowProcessController = mock(WindowProcessController.class);
+ when(mService.mActivityTaskManagerInternal.getTopApp())
+ .thenReturn(windowProcessController);
+ when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+ }
+
+ private void flushHandler() {
+ flushHandler(1);
+ }
+
+ private void flushHandler(int count) {
+ for (int i = 0; i < count; i++) {
+ mService.getHandler().runWithScissors(() -> {}, 0);
+ }
}
@Test
@@ -80,6 +110,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
@@ -92,6 +123,7 @@ public final class DeviceStateManagerServiceTest {
mPolicy.blockConfigure();
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
@@ -99,6 +131,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+ flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -106,6 +139,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
+ flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -149,6 +183,7 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
+ flushHandler();
// The current committed and requests states do not change because the current state remains
// supported.
@@ -166,6 +201,7 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().registerCallback(callback);
// An initial callback will be triggered on registration, so we clear it here.
+ flushHandler();
callback.clearLastNotifiedInfo();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
@@ -174,6 +210,7 @@ public final class DeviceStateManagerServiceTest {
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE });
+ flushHandler();
// The current committed and requests states do not change because the current state remains
// supported.
@@ -203,12 +240,14 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().registerCallback(callback);
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ flushHandler();
assertEquals(callback.getLastNotifiedInfo().baseState,
OTHER_DEVICE_STATE.getIdentifier());
assertEquals(callback.getLastNotifiedInfo().currentState,
OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(DEFAULT_DEVICE_STATE.getIdentifier());
+ flushHandler();
assertEquals(callback.getLastNotifiedInfo().baseState,
DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(callback.getLastNotifiedInfo().currentState,
@@ -216,6 +255,7 @@ public final class DeviceStateManagerServiceTest {
mPolicy.blockConfigure();
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ flushHandler();
// The callback should not have been notified of the state change as the policy is still
// pending callback.
assertEquals(callback.getLastNotifiedInfo().baseState,
@@ -224,6 +264,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
+ flushHandler();
// Now that the policy is finished processing the callback should be notified of the state
// change.
assertEquals(callback.getLastNotifiedInfo().baseState,
@@ -236,6 +277,7 @@ public final class DeviceStateManagerServiceTest {
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.getIdentifier());
@@ -247,6 +289,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
+ flushHandler();
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
@@ -254,6 +297,10 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
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(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -271,6 +318,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mService.getBinderService().cancelRequest(token);
+ flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_CANCELED);
@@ -287,10 +335,12 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier());
}
+ @FlakyTest(bugId = 200332057)
@Test
public void requestState_pendingStateAtRequest() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
+ flushHandler();
mPolicy.blockConfigure();
@@ -303,6 +353,10 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(firstRequestToken,
OTHER_DEVICE_STATE.getIdentifier(), 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));
@@ -312,8 +366,8 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(secondRequestToken,
DEFAULT_DEVICE_STATE.getIdentifier(), 0 /* flags */);
-
mPolicy.resumeConfigureOnce();
+ flushHandler();
// First request status is now suspended as there is another pending request.
assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
@@ -330,6 +384,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier());
mPolicy.resumeConfigure();
+ flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
@@ -339,6 +394,7 @@ public final class DeviceStateManagerServiceTest {
// Now cancel the second request to make the first request active.
mService.getBinderService().cancelRequest(secondRequestToken);
+ flushHandler();
assertEquals(callback.getLastNotifiedStatus(firstRequestToken),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -356,6 +412,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_sameAsBaseState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
+ flushHandler();
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
@@ -363,6 +420,7 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, DEFAULT_DEVICE_STATE.getIdentifier(),
0 /* flags */);
+ flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -372,6 +430,7 @@ public final class DeviceStateManagerServiceTest {
public void requestState_flagCancelWhenBaseChanges() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
+ flushHandler();
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
@@ -379,6 +438,10 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
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);
@@ -391,6 +454,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mProvider.setState(OTHER_DEVICE_STATE.getIdentifier());
+ flushHandler();
// Request is canceled because the base state changed.
assertEquals(callback.getLastNotifiedStatus(token),
@@ -403,10 +467,12 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
}
+ @FlakyTest(bugId = 200332057)
@Test
public void requestState_becomesUnsupported() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
+ flushHandler();
final IBinder token = new Binder();
assertEquals(callback.getLastNotifiedStatus(token),
@@ -414,6 +480,7 @@ public final class DeviceStateManagerServiceTest {
mService.getBinderService().requestState(token, OTHER_DEVICE_STATE.getIdentifier(),
0 /* flags */);
+ flushHandler();
assertEquals(callback.getLastNotifiedStatus(token),
TestDeviceStateManagerCallback.STATUS_ACTIVE);
@@ -425,6 +492,7 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
mProvider.notifySupportedDeviceStates(new DeviceState[]{ DEFAULT_DEVICE_STATE });
+ flushHandler();
// Request is canceled because the state is no longer supported.
assertEquals(callback.getLastNotifiedStatus(token),
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
index b5c8053ad77e..e286cb27cc41 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
@@ -41,24 +41,26 @@ public final class DeviceStateTest {
@Test
public void testConstruct() {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE /* identifier */,
- "CLOSED" /* name */);
+ "CLOSED" /* name */, DeviceState.FLAG_CANCEL_STICKY_REQUESTS /* flags */);
assertEquals(state.getIdentifier(), MINIMUM_DEVICE_STATE);
assertEquals(state.getName(), "CLOSED");
+ assertEquals(state.getFlags(), DeviceState.FLAG_CANCEL_STICKY_REQUESTS);
}
@Test
public void testConstruct_nullName() {
final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE /* identifier */,
- null /* name */);
+ null /* name */, 0/* flags */);
assertEquals(state.getIdentifier(), MAXIMUM_DEVICE_STATE);
assertNull(state.getName());
+ assertEquals(state.getFlags(), 0);
}
@Test
public void testConstruct_tooLargeIdentifier() {
assertThrows(IllegalArgumentException.class, () -> {
final DeviceState state = new DeviceState(MAXIMUM_DEVICE_STATE + 1 /* identifier */,
- null /* name */);
+ null /* name */, 0 /* flags */);
});
}
@@ -66,7 +68,7 @@ public final class DeviceStateTest {
public void testConstruct_tooSmallIdentifier() {
assertThrows(IllegalArgumentException.class, () -> {
final DeviceState state = new DeviceState(MINIMUM_DEVICE_STATE - 1 /* identifier */,
- null /* name */);
+ null /* name */, 0 /* flags */);
});
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
new file mode 100644
index 000000000000..c9cf2f06640d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.devicestate;
+
+import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link OverrideRequestController}.
+ * <p/>
+ * Run with <code>atest OverrideRequestControllerTest</code>.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class OverrideRequestControllerTest {
+ private TestStatusChangeListener mStatusListener;
+ private OverrideRequestController mController;
+
+ @Before
+ public void setup() {
+ mStatusListener = new TestStatusChangeListener();
+ mController = new OverrideRequestController(mStatusListener);
+ }
+
+ @Test
+ public void addRequest() {
+ OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ assertNull(mStatusListener.getLastStatus(request));
+
+ mController.addRequest(request);
+ assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE);
+ }
+
+ @Test
+ public void addRequest_suspendExistingRequest() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ assertNull(mStatusListener.getLastStatus(firstRequest));
+
+ mController.addRequest(firstRequest);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ assertNull(mStatusListener.getLastStatus(secondRequest));
+
+ mController.addRequest(secondRequest);
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+ }
+
+ @Test
+ public void addRequest_cancelActiveRequest() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.cancelRequest(secondRequest.getToken());
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ }
+
+ @Test
+ public void addRequest_cancelSuspendedRequest() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.cancelRequest(firstRequest.getToken());
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ }
+
+ @Test
+ public void handleBaseStateChanged() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */,
+ DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.handleBaseStateChanged();
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ }
+
+ @Test
+ public void handleProcessDied() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.handleProcessDied(1);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.handleProcessDied(0);
+
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ }
+
+ @Test
+ public void handleProcessDied_stickyRequests() {
+ mController.setStickyRequestsAllowed(true);
+
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */,
+ 0 /* requestedState */, 0 /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.handleProcessDied(1);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.cancelStickyRequests();
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+ }
+
+ @Test
+ public void handleNewSupportedStates() {
+ OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 1 /* requestedState */, 0 /* flags */);
+ OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+ 2 /* requestedState */, 0 /* flags */);
+
+ mController.addRequest(firstRequest);
+ mController.addRequest(secondRequest);
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED);
+
+ mController.handleNewSupportedStates(new int[]{ 0, 1 });
+
+ assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED);
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+ mController.handleNewSupportedStates(new int[]{ 0 });
+
+ assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+ }
+
+ private static final class TestStatusChangeListener implements
+ OverrideRequestController.StatusChangeListener {
+ private Map<OverrideRequest, Integer> mLastStatusMap = new HashMap<>();
+
+ @Override
+ public void onStatusChanged(@NonNull OverrideRequest request, int newStatus) {
+ mLastStatusMap.put(request, newStatus);
+ }
+
+ @Nullable
+ public Integer getLastStatus(OverrideRequest request) {
+ return mLastStatusMap.get(request);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 5ba375b922e2..7c55716c5e99 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -885,6 +885,61 @@ public class DisplayManagerServiceTest {
assertFalse(callback.mDisplayAddedCalled);
}
+
+
+ @Test
+ public void testSettingTwoBrightnessConfigurationsOnMultiDisplay() {
+ Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+ // get the first two internal displays
+ Display[] displays = displayManager.getDisplays();
+ Display internalDisplayOne = null;
+ Display internalDisplayTwo = null;
+ for (Display display : displays) {
+ if (display.getType() == Display.TYPE_INTERNAL) {
+ if (internalDisplayOne == null) {
+ internalDisplayOne = display;
+ } else {
+ internalDisplayTwo = display;
+ break;
+ }
+ }
+ }
+
+ // return if there are fewer than 2 displays on this device
+ if (internalDisplayOne == null || internalDisplayTwo == null) {
+ return;
+ }
+
+ final String uniqueDisplayIdOne = internalDisplayOne.getUniqueId();
+ final String uniqueDisplayIdTwo = internalDisplayTwo.getUniqueId();
+
+ BrightnessConfiguration configOne =
+ new BrightnessConfiguration.Builder(
+ new float[]{0.0f, 12345.0f}, new float[]{15.0f, 400.0f})
+ .setDescription("model:1").build();
+ BrightnessConfiguration configTwo =
+ new BrightnessConfiguration.Builder(
+ new float[]{0.0f, 6789.0f}, new float[]{12.0f, 300.0f})
+ .setDescription("model:2").build();
+
+ displayManager.setBrightnessConfigurationForDisplay(configOne,
+ uniqueDisplayIdOne);
+ displayManager.setBrightnessConfigurationForDisplay(configTwo,
+ uniqueDisplayIdTwo);
+
+ BrightnessConfiguration configFromOne =
+ displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdOne);
+ BrightnessConfiguration configFromTwo =
+ displayManager.getBrightnessConfigurationForDisplay(uniqueDisplayIdTwo);
+
+ assertNotNull(configFromOne);
+ assertEquals(configOne, configFromOne);
+ assertEquals(configTwo, configFromTwo);
+
+ }
+
private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
throws Exception {
DisplayManagerService displayManager =
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 196454bd32ce..57a9cb278c80 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -17,13 +17,16 @@
package com.android.server.display;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.content.Context;
import android.hardware.display.BrightnessConfiguration;
import android.util.Pair;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -144,15 +147,93 @@ public class PersistentDataStoreTest {
}
@Test
+ public void testStoreAndReloadOfDisplayBrightnessConfigurations() {
+ final String uniqueDisplayId = "test:123";
+ int userSerial = 0;
+ String packageName = "pdsTestPackage";
+ final float[] lux = { 0f, 10f };
+ final float[] nits = {1f, 100f };
+ final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+ .setDescription("a description")
+ .build();
+ mDataStore.loadIfNeeded();
+ assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+ userSerial));
+
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+
+ mDataStore.setBrightnessConfigurationForDisplayLocked(config, testDisplayDevice, userSerial,
+ packageName);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertNotNull(newDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+ userSerial));
+ assertEquals(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+ userSerial), newDataStore.getBrightnessConfigurationForDisplayLocked(
+ uniqueDisplayId, userSerial));
+ }
+
+ @Test
+ public void testSetBrightnessConfigurationFailsWithUnstableId() {
+ final String uniqueDisplayId = "test:123";
+ int userSerial = 0;
+ String packageName = "pdsTestPackage";
+ final float[] lux = { 0f, 10f };
+ final float[] nits = {1f, 100f };
+ final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
+ .setDescription("a description")
+ .build();
+ mDataStore.loadIfNeeded();
+ assertNull(mDataStore.getBrightnessConfigurationForDisplayLocked(uniqueDisplayId,
+ userSerial));
+
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return false;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+
+ assertFalse(mDataStore.setBrightnessConfigurationForDisplayLocked(
+ config, testDisplayDevice, userSerial, packageName));
+ }
+
+ @Test
public void testStoreAndReloadOfBrightnessConfigurations() {
final float[] lux = { 0f, 10f };
final float[] nits = {1f, 100f };
final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
.setDescription("a description")
.build();
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ String packageName = context.getPackageName();
+
mDataStore.loadIfNeeded();
assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
- mDataStore.setBrightnessConfigurationForUser(config, 0, "packagename");
+ mDataStore.setBrightnessConfigurationForUser(config, 0, packageName);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
mInjector.setWriteStream(baos);
@@ -173,17 +254,18 @@ public class PersistentDataStoreTest {
public void testNullBrightnessConfiguration() {
final float[] lux = { 0f, 10f };
final float[] nits = {1f, 100f };
+ int userSerial = 0;
final BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
.setDescription("a description")
.build();
mDataStore.loadIfNeeded();
- assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+ assertNull(mDataStore.getBrightnessConfiguration(userSerial));
- mDataStore.setBrightnessConfigurationForUser(config, 0, "packagename");
- assertNotNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+ mDataStore.setBrightnessConfigurationForUser(config, userSerial, "packagename");
+ assertNotNull(mDataStore.getBrightnessConfiguration(userSerial));
- mDataStore.setBrightnessConfigurationForUser(null, 0, "packagename");
- assertNull(mDataStore.getBrightnessConfiguration(0 /*userSerial*/));
+ mDataStore.setBrightnessConfigurationForUser(null, userSerial, "packagename");
+ assertNull(mDataStore.getBrightnessConfiguration(userSerial));
}
public class TestInjector extends PersistentDataStore.Injector {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 976a588273a7..18992ecfd3a4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
@@ -27,9 +29,12 @@ import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.content.IIntentReceiver;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.SparseArray;
import androidx.test.runner.AndroidJUnit4;
@@ -62,8 +67,18 @@ import java.util.regex.Pattern;
// bit FrameworksServicesTests:com.android.server.pm.PackageManagerServiceTest
@RunWith(AndroidJUnit4.class)
public class PackageManagerServiceTest {
+
+ private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
+
+ private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/";
+ private static final String TEST_APP_APK = "StubTestApp.apk";
+ private static final String TEST_PKG_NAME = "com.android.servicestests.apps.stubapp";
+
+ private IPackageManager mIPackageManager;
+
@Before
public void setUp() throws Exception {
+ mIPackageManager = AppGlobals.getPackageManager();
}
@After
@@ -599,4 +614,26 @@ public class PackageManagerServiceTest {
Collections.sort(knownPackageIds);
return knownPackageIds;
}
+
+ @Test
+ public void testSetSplashScreenTheme_samePackage_succeeds() throws Exception {
+ mIPackageManager.setSplashScreenTheme(PACKAGE_NAME, null /* themeName */,
+ UserHandle.myUserId());
+ // Invoking setSplashScreenTheme on the same package shouldn't get any exception.
+ }
+
+ @Test
+ public void testSetSplashScreenTheme_differentPackage_fails() throws Exception {
+ final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK);
+ try {
+ runShellCommand("pm install " + testApk);
+ mIPackageManager.setSplashScreenTheme(TEST_PKG_NAME, null /* themeName */,
+ UserHandle.myUserId());
+ fail("setSplashScreenTheme did not throw SecurityException as expected");
+ } catch (SecurityException e) {
+ // expected
+ } finally {
+ runShellCommand("pm uninstall " + TEST_PKG_NAME);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 581ff5472e92..9099bb515361 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -341,10 +341,8 @@ open class AndroidPackageParsingTestBase {
launchToken=${this.launchToken}
lockTaskLaunchMode=${this.lockTaskLaunchMode}
logo=${this.logo}
- maxAspectRatio=${this.maxAspectRatio}
maxRecents=${this.maxRecents}
metaData=${this.metaData.dumpToString()}
- minAspectRatio=${this.minAspectRatio}
name=${this.name}
nonLocalizedLabel=${
// Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 4d2d2f1a4b7d..761cea79df28 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -150,8 +150,9 @@ public final class DeviceStateProviderImplTest {
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
- final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""),
- new DeviceState(2, "") };
+ final DeviceState[] expectedStates = new DeviceState[]{
+ new DeviceState(1, "", 0 /* flags */),
+ new DeviceState(2, "", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
verify(listener).onStateChanged(mIntegerCaptor.capture());
@@ -159,6 +160,64 @@ public final class DeviceStateProviderImplTest {
}
@Test
+ public void create_stateWithCancelStickyRequestFlag() {
+ String configString = "<device-state-config>\n"
+ + " <device-state>\n"
+ + " <identifier>1</identifier>\n"
+ + " <flags>\n"
+ + " <flag>FLAG_CANCEL_STICKY_REQUESTS</flag>\n"
+ + " </flags>\n"
+ + " <conditions/>\n"
+ + " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>2</identifier>\n"
+ + " <conditions/>\n"
+ + " </device-state>\n"
+ + "</device-state-config>\n";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+ DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ config);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ final DeviceState[] expectedStates = new DeviceState[]{
+ new DeviceState(1, "", DeviceState.FLAG_CANCEL_STICKY_REQUESTS),
+ new DeviceState(2, "", 0 /* flags */) };
+ assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
+ }
+
+ @Test
+ public void create_stateWithInvalidFlag() {
+ String configString = "<device-state-config>\n"
+ + " <device-state>\n"
+ + " <identifier>1</identifier>\n"
+ + " <flags>\n"
+ + " <flag>INVALID_FLAG</flag>\n"
+ + " </flags>\n"
+ + " <conditions/>\n"
+ + " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>2</identifier>\n"
+ + " <conditions/>\n"
+ + " </device-state>\n"
+ + "</device-state-config>\n";
+ DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+ DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ config);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ final DeviceState[] expectedStates = new DeviceState[]{
+ new DeviceState(1, "", 0 /* flags */),
+ new DeviceState(2, "", 0 /* flags */) };
+ assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
+ }
+
+ @Test
public void create_lidSwitch() {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
@@ -187,8 +246,9 @@ public final class DeviceStateProviderImplTest {
provider.setListener(listener);
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
- final DeviceState[] expectedStates = new DeviceState[]{ new DeviceState(1, ""),
- new DeviceState(2, "CLOSED") };
+ final DeviceState[] expectedStates = new DeviceState[]{
+ new DeviceState(1, "", 0 /* flags */),
+ new DeviceState(2, "CLOSED", 0 /* flags */) };
assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
@@ -264,8 +324,11 @@ public final class DeviceStateProviderImplTest {
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
assertArrayEquals(
- new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"),
- new DeviceState(3, "OPENED") }, mDeviceStateArrayCaptor.getValue());
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */) },
+ mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state.
verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
@@ -350,8 +413,10 @@ public final class DeviceStateProviderImplTest {
verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
assertArrayEquals(
- new DeviceState[]{ new DeviceState(1, "CLOSED"), new DeviceState(2, "HALF_OPENED"),
- }, mDeviceStateArrayCaptor.getValue());
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */)
+ }, mDeviceStateArrayCaptor.getValue());
// onStateChanged() should be called because the provider could not find the sensor.
verify(listener).onStateChanged(mIntegerCaptor.capture());
assertEquals(1, mIntegerCaptor.getValue().intValue());
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 26b34fdd4e04..304fe5a1c9c3 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -30,6 +30,7 @@ import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.State;
import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
+import android.os.Looper;
import androidx.test.InstrumentationRegistry;
@@ -145,12 +146,12 @@ public class PowerStatsServiceTest {
}
@Override
- PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
- String meterFilename, String meterCacheFilename,
+ PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
+ File dataStoragePath, String meterFilename, String meterCacheFilename,
String modelFilename, String modelCacheFilename,
String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
- mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath,
+ mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath,
meterFilename, meterCacheFilename,
modelFilename, modelCacheFilename,
residencyFilename, residencyCacheFilename,
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java
new file mode 100644
index 000000000000..38297bf624b7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/UptimeTimerTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@RunWith(AndroidJUnit4.class)
+public class UptimeTimerTest {
+ private static final String TAG = "UptimeTimerTest";
+
+ @Test
+ public void testBasic() throws InterruptedException {
+ AtomicBoolean taskRan = new AtomicBoolean(false);
+ UptimeTimer timer = new UptimeTimer("TestTimer");
+ timer.createTask(() -> taskRan.set(true), 100);
+ Thread.sleep(50);
+ boolean before = taskRan.get();
+ Thread.sleep(100);
+ boolean after = taskRan.get();
+ assertFalse(before);
+ assertTrue(after);
+ }
+
+ @Test
+ public void testCancel() throws InterruptedException {
+ AtomicBoolean taskRan = new AtomicBoolean(false);
+ UptimeTimer timer = new UptimeTimer("TestTimer");
+ UptimeTimer.Task task = timer.createTask(() -> taskRan.set(true), 100);
+ Thread.sleep(50);
+ boolean before = taskRan.get();
+ task.cancel();
+ Thread.sleep(100);
+ boolean after = taskRan.get();
+ assertFalse(before);
+ assertFalse(after);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 5a00e0d6530d..62e0a19abd7f 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -998,6 +998,8 @@ public class VibratorManagerServiceTest {
throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
createSystemReadyService();
IExternalVibrationController firstController = mock(IExternalVibrationController.class);
@@ -1006,8 +1008,11 @@ public class VibratorManagerServiceTest {
firstController);
int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
- ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
- secondController);
+ AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME,
+ ringtoneAudioAttrs, secondController);
int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
assertEquals(IExternalVibratorService.SCALE_NONE, firstScale);
@@ -1040,6 +1045,37 @@ public class VibratorManagerServiceTest {
assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates());
}
+ @Test
+ public void onExternalVibration_withRingtone_usesRingerModeSettings() {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ mVibrator.setDefaultRingVibrationIntensity(Vibrator.VIBRATION_INTENSITY_MEDIUM);
+ AudioAttributes audioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
+ ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+
+ setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+
+ setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
+ setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0);
+ createSystemReadyService();
+ scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
index 78afb7b72c04..3cc105ebb746 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -18,6 +18,7 @@
package="com.android.servicestests.apps.simpleservicetestapp">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
<service android:name=".SimpleService"
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
index 4e981b22cd32..b8654d7f4e74 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
@@ -17,8 +17,10 @@ package com.android.servicestests.apps.simpleservicetestapp;
import android.app.Service;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -33,6 +35,9 @@ public class SimpleService extends Service {
private static final String TEST_CLASS =
"com.android.servicestests.apps.simpleservicetestapp.SimpleService";
+ private static final String ACTION_SERVICE_WITH_DEP_PKG =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG";
+
private static final String EXTRA_CALLBACK = "callback";
private static final String EXTRA_COMMAND = "command";
private static final String EXTRA_FLAGS = "flags";
@@ -118,6 +123,21 @@ public class SimpleService extends Service {
@Override
public IBinder onBind(Intent intent) {
+ if (ACTION_SERVICE_WITH_DEP_PKG.equals(intent.getAction())) {
+ final String targetPkg = intent.getStringExtra(EXTRA_TARGET_PACKAGE);
+ Log.i(TAG, "SimpleService.onBind: " + ACTION_SERVICE_WITH_DEP_PKG + " " + targetPkg);
+ if (targetPkg != null) {
+ Context pkgContext = null;
+ try {
+ pkgContext = createPackageContext(targetPkg,
+ Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to create package context for " + pkgContext, e);
+ }
+ // This effectively loads the target package as a dependency.
+ pkgContext.getClassLoader();
+ }
+ }
return mBinder;
}
}
diff --git a/services/tests/servicestests/test-apps/StubApp/Android.bp b/services/tests/servicestests/test-apps/StubApp/Android.bp
new file mode 100644
index 000000000000..99deb3f5bbf0
--- /dev/null
+++ b/services/tests/servicestests/test-apps/StubApp/Android.bp
@@ -0,0 +1,37 @@
+// 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 {
+ // 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: "StubTestApp",
+
+ sdk_version: "current",
+
+ srcs: ["**/*.java"],
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml
new file mode 100644
index 000000000000..90172e77f958
--- /dev/null
+++ b/services/tests/servicestests/test-apps/StubApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.stubapp">
+
+ <application android:label="StubTestApp">
+ <activity android:name=".TestActivity"
+ android:exported="true" />
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java
new file mode 100644
index 000000000000..0d94676aeb52
--- /dev/null
+++ b/services/tests/servicestests/test-apps/StubApp/src/com/android/servicestests/apps/stubapp/TestActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.servicestests.apps.stubapp;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 4b3771b95c05..f21991defbec 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -475,7 +475,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_failsForBogusPackageName() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
.thenReturn(TestInjector.CALLING_UID + 1);
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
@@ -485,7 +485,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_failsIfNameNotFound() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
.thenThrow(new PackageManager.NameNotFoundException());
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
@@ -495,7 +495,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_failsIfNoProjectionTypes() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
assertThrows(IllegalArgumentException.class,
() -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME));
@@ -507,7 +508,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_failsIfMultipleProjectionTypes() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
// Don't use PROJECTION_TYPE_ALL because that's actually == -1 and will fail the > 0 check.
int multipleProjectionTypes = PROJECTION_TYPE_AUTOMOTIVE | 0x0002 | 0x0004;
@@ -522,7 +524,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception {
- doThrow(new SecurityException()).when(mPackageManager).getPackageUid(PACKAGE_NAME, 0);
+ doThrow(new SecurityException())
+ .when(mPackageManager).getPackageUidAsUser(eq(PACKAGE_NAME), anyInt());
assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
@@ -531,12 +534,14 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_automotive_failsIfAlreadySetByOtherPackage() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
String otherPackage = "Raconteurs";
- when(mPackageManager.getPackageUid(otherPackage, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(otherPackage), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, otherPackage));
assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE),
contains(PACKAGE_NAME));
@@ -544,7 +549,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection_failsIfCannotLinkToDeath() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
doThrow(new RemoteException()).when(mBinder).linkToDeath(any(), anyInt());
assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
@@ -553,7 +559,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void requestProjection() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
// Should work for all powers of two.
for (int i = 0; i < Integer.SIZE; ++i) {
int projectionType = 1 << i;
@@ -568,11 +575,12 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void releaseProjection_failsForBogusPackageName() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
.thenReturn(TestInjector.CALLING_UID + 1);
assertThrows(SecurityException.class, () -> mService.releaseProjection(
@@ -582,10 +590,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void releaseProjection_failsIfNameNotFound() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0))
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
.thenThrow(new PackageManager.NameNotFoundException());
assertThrows(SecurityException.class, () -> mService.releaseProjection(
@@ -595,7 +604,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void releaseProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
doThrow(new SecurityException()).when(mContext).enforceCallingPermission(
@@ -613,7 +623,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void releaseProjection() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
requestAllPossibleProjectionTypes();
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
@@ -632,7 +643,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void binderDeath_releasesProjection() throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
requestAllPossibleProjectionTypes();
assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor = ArgumentCaptor.forClass(
@@ -647,7 +659,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void getActiveProjectionTypes() throws Exception {
assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
@@ -657,7 +670,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void getProjectingPackages() throws Exception {
assertTrue(mService.getProjectingPackages(PROJECTION_TYPE_ALL).isEmpty());
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE).size());
assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_ALL).size());
@@ -681,7 +695,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
@Test
public void addOnProjectionStateChangedListener_callsListenerIfProjectionActive()
throws Exception {
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
@@ -710,7 +725,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
mService.removeOnProjectionStateChangedListener(listener);
// Now set automotive projection, should not call back.
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
verify(listener, never()).onProjectionStateChanged(anyInt(), any());
}
@@ -726,7 +742,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verifyNoMoreInteractions(listener);
// Now set automotive projection, should call back.
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE),
eq(List.of(PACKAGE_NAME)));
@@ -752,8 +769,9 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
int fakeProjectionType = 0x0002;
int otherFakeProjectionType = 0x0004;
String otherPackageName = "Internet Arms";
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
- when(mPackageManager.getPackageUid(otherPackageName, 0))
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(otherPackageName), anyInt()))
.thenReturn(TestInjector.CALLING_UID);
IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
@@ -806,7 +824,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
// Now kill the binder for the listener. This should remove it from the list of listeners.
listenerDeathRecipient.getValue().binderDied();
- when(mPackageManager.getPackageUid(PACKAGE_NAME, 0)).thenReturn(TestInjector.CALLING_UID);
+ when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(TestInjector.CALLING_UID);
mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
verify(listener, never()).onProjectionStateChanged(anyInt(), any());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index ea46eab6e8f9..d593e8000048 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,7 +32,6 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -40,6 +39,7 @@ import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.after;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -48,6 +48,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -73,7 +74,6 @@ import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -102,6 +102,7 @@ import java.util.Objects;
@SmallTest
@RunWith(AndroidJUnit4.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
public class BuzzBeepBlinkTest extends UiServiceTestCase {
@Mock AudioManager mAudioManager;
@@ -156,6 +157,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mAudioManager.getFocusRampTimeMs(anyInt(), any(AudioAttributes.class))).thenReturn(50);
when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
when(mVibrator.hasFrequencyControl()).thenReturn(false);
when(mKeyguardManager.isDeviceLocked(anyInt())).thenReturn(false);
@@ -444,6 +446,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
timeout(MAX_VIBRATION_DELAY).times(1));
}
+ private void verifyDelayedNeverVibrate() {
+ verify(mVibrator, after(MAX_VIBRATION_DELAY).never()).vibrate(anyInt(), anyString(), any(),
+ anyString(), any(AudioAttributes.class));
+ }
+
private void verifyVibrate(ArgumentMatcher<VibrationEffect> effectMatcher,
VerificationMode verification) {
ArgumentCaptor<AudioAttributes> captor = ArgumentCaptor.forClass(AudioAttributes.class);
@@ -1588,8 +1595,51 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
// beep wasn't reset
verifyNeverBeep();
verifyNeverVibrate();
- verify(mRingtonePlayer, never()).stopAsync();
- verify(mVibrator, never()).cancel();
+ verifyNeverStopAudio();
+ verifyNeverStopVibrate();
+ }
+
+ @Test
+ public void testRingtoneInsistentBeep_clearEffectsStopsSoundAndVibration() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+ verifyDelayedVibrateLooped();
+
+ mService.clearSoundLocked();
+ mService.clearVibrateLocked();
+
+ verifyStopAudio();
+ verifyStopVibrate();
+ }
+
+ @Test
+ public void testRingtoneInsistentBeep_neverVibratesWhenEffectsClearedBeforeDelay()
+ throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+ verifyNeverVibrate();
+
+ mService.clearSoundLocked();
+ mService.clearVibrateLocked();
+
+ verifyStopAudio();
+ verifyDelayedNeverVibrate();
}
@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 987236c7c98c..c337ccd67db8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -379,6 +379,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
/** Test that restore correctly parses the user_set attribute. */
@Test
public void testReadXml_restoresUserSet() throws Exception {
+ mVersionString = "4";
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service =
new TestManagedServices(
@@ -1513,7 +1514,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) {
String pkgOrCmp = mExpectedPrimary.get(service.mApprovalLevel).get(userId);
xml.append(getXmlEntry(
- pkgOrCmp, userId, true, !(pkgOrCmp.startsWith("non.user.set.package"))));
+ pkgOrCmp, userId, true,
+ !(pkgOrCmp.startsWith("non.user.set.package"))));
}
for (int userId : mExpectedSecondary.get(service.mApprovalLevel).keySet()) {
xml.append(getXmlEntry(
@@ -1541,7 +1543,9 @@ public class ManagedServicesTest extends UiServiceTestCase {
private TypedXmlPullParser getParserWithEntries(ManagedServices service, String... xmlEntries)
throws Exception {
final StringBuffer xml = new StringBuffer();
- xml.append("<" + service.getConfig().xmlTag + ">\n");
+ xml.append("<" + service.getConfig().xmlTag
+ + (mVersionString != null ? " version=\"" + mVersionString + "\" " : "")
+ + ">\n");
for (String xmlEntry : xmlEntries) {
xml.append(xmlEntry);
}
@@ -1726,12 +1730,19 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
private String getXmlEntry(String approved, int userId, boolean isPrimary, boolean userSet) {
+ String userSetString = "";
+ if (mVersionString.equals("4")) {
+ userSetString =
+ ManagedServices.ATT_USER_CHANGED + "=\"" + String.valueOf(userSet) + "\" ";
+ } else if (mVersionString.equals("3")) {
+ userSetString =
+ ManagedServices.ATT_USER_SET + "=\"" + (userSet ? approved : "") + "\" ";
+ }
return "<" + ManagedServices.TAG_MANAGED_SERVICES + " "
+ ManagedServices.ATT_USER_ID + "=\"" + userId +"\" "
+ ManagedServices.ATT_IS_PRIMARY + "=\"" + isPrimary +"\" "
+ ManagedServices.ATT_APPROVED_LIST + "=\"" + approved +"\" "
- + ManagedServices.ATT_USER_SET + "=\"" + (userSet ? approved : "") + "\" "
- + "/>\n";
+ + userSetString + "/>\n";
}
class TestManagedServices extends ManagedServices {
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 054a401d41af..4b93e35e673a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@@ -124,6 +125,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
profileIds.add(12);
when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
when(mNm.isNASMigrationDone(anyInt())).thenReturn(true);
+ when(mNm.canUseManagedServices(any(), anyInt(), any())).thenReturn(true);
}
@Test
@@ -178,6 +180,92 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
}
@Test
+ public void testReadXml_upgradeUserSet_preS_VersionThree() throws Exception {
+ String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
+ + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+ + "user_set=\"true\"/>"
+ + "</enabled_assistants>";
+
+ 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);
+
+ verify(mAssistants, times(0)).upgradeUserSet();
+ assertTrue(isUserSetServicesEmpty(mAssistants, 0));
+ assertTrue(mAssistants.mIsUserChanged.get(0));
+ }
+
+ @Test
+ public void testReadXml_upgradeUserSet_preS_VersionOne() throws Exception {
+ String xml = "<enabled_assistants version=\"1\" defaults=\"b/b\">"
+ + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+ + "user_set=\"true\"/>"
+ + "</enabled_assistants>";
+
+ 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);
+
+ verify(mAssistants, times(0)).upgradeUserSet();
+ assertTrue(isUserSetServicesEmpty(mAssistants, 0));
+ assertTrue(mAssistants.mIsUserChanged.get(0));
+ }
+
+ @Test
+ public void testReadXml_upgradeUserSet_preS_noUserSet() throws Exception {
+ String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
+ + "<service_listing approved=\"b/b\" user=\"0\" primary=\"true\"/>"
+ + "</enabled_assistants>";
+
+ 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);
+
+ verify(mAssistants, times(1)).upgradeUserSet();
+ assertTrue(isUserSetServicesEmpty(mAssistants, 0));
+ assertFalse(mAssistants.mIsUserChanged.get(0));
+ }
+
+ @Test
+ public void testReadXml_upgradeUserSet_preS_noUserSet_diffDefault() throws Exception {
+ String xml = "<enabled_assistants version=\"3\" defaults=\"a/a\">"
+ + "<service_listing approved=\"b/b\" user=\"0\" primary=\"true\"/>"
+ + "</enabled_assistants>";
+
+ 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);
+
+ verify(mAssistants, times(1)).upgradeUserSet();
+ assertTrue(isUserSetServicesEmpty(mAssistants, 0));
+ assertFalse(mAssistants.mIsUserChanged.get(0));
+ assertEquals(new ArraySet<>(Arrays.asList(new ComponentName("a", "a"))),
+ mAssistants.getDefaultComponents());
+ assertEquals(Arrays.asList(new ComponentName("b", "b")),
+ mAssistants.getAllowedComponents(0));
+ }
+
+ @Test
public void testReadXml_multiApproved() throws Exception {
String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ "<service_listing approved=\"a/a:b/b\" user=\"0\" primary=\"true\""
@@ -210,7 +298,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
verify(mNm, never()).setDefaultAssistantForUser(anyInt());
verify(mAssistants, times(1)).addApprovedList(
- new ComponentName("b", "b").flattenToString(), 10, true, null);
+ new ComponentName("b", "b").flattenToString(), 10, true, "");
}
@Test
@@ -380,4 +468,11 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
verify(mNm, times(1)).setDefaultAssistantForUser(eq(mZero.id));
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();
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 577e36c7d5db..a834e2b6cc5a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -118,7 +118,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
assertEquals(canBubble(i), ranking.canBubble());
- assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
+ assertEquals(isTextChanged(i), ranking.isTextChanged());
assertEquals(isConversation(i), ranking.isConversation());
assertEquals(getShortcutInfo(i).getId(), ranking.getConversationShortcutInfo().getId());
assertEquals(getRankingAdjustment(i), ranking.getRankingAdjustment());
@@ -189,7 +189,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
(ArrayList) tweak.getSmartActions(),
(ArrayList) tweak.getSmartReplies(),
tweak.canBubble(),
- tweak.visuallyInterruptive(),
+ tweak.isTextChanged(),
tweak.isConversation(),
tweak.getConversationShortcutInfo(),
tweak.getRankingAdjustment(),
@@ -270,7 +270,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
getSmartActions(key, i),
getSmartReplies(key, i),
canBubble(i),
- visuallyInterruptive(i),
+ isTextChanged(i),
isConversation(i),
getShortcutInfo(i),
getRankingAdjustment(i),
@@ -379,7 +379,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
return index % 4 == 0;
}
- private boolean visuallyInterruptive(int index) {
+ private boolean isTextChanged(int index) {
return index % 4 == 0;
}
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 1ae219db7726..e98d077836e0 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -61,10 +61,6 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
-import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS;
-import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS;
-import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -97,6 +93,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -225,12 +222,15 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final int UID_HEADLESS = 1000000;
@@ -325,7 +325,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
MultiRateLimiter mToastRateLimiter;
BroadcastReceiver mPackageIntentReceiver;
- BroadcastReceiver mNASIntentReceiver;
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@@ -553,14 +552,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
&& filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED)
&& filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
mPackageIntentReceiver = broadcastReceivers.get(i);
- } else if (filter.hasAction(ACTION_ENABLE_NAS)
- && filter.hasAction(ACTION_DISABLE_NAS)
- && filter.hasAction(ACTION_LEARNMORE_NAS)) {
- mNASIntentReceiver = broadcastReceivers.get(i);
}
}
assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
- assertNotNull("nas intent receiver should exist", mNASIntentReceiver);
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -655,16 +649,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mPackageIntentReceiver.onReceive(getContext(), intent);
}
- private void simulateNASUpgradeBroadcast(String action, int uid) {
- final Bundle extras = new Bundle();
- extras.putInt(Intent.EXTRA_USER_ID, uid);
-
- final Intent intent = new Intent(action);
- intent.putExtras(extras);
-
- mNASIntentReceiver.onReceive(getContext(), intent);
- }
-
private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
changed.put(true, new ArrayList<>());
@@ -2460,7 +2444,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(associations);
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
+ when(mPreferencesHelper.getNotificationChannelGroupWithChannels(
+ eq(PKG), anyInt(), eq(ncg.getId()), anyBoolean()))
.thenReturn(ncg);
reset(mListeners);
mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId());
@@ -2470,6 +2455,56 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testDeleteChannelGroupChecksForFgses() throws Exception {
+ List<String> associations = new ArrayList<>();
+ associations.add("a");
+ when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ .thenReturn(associations);
+ CountDownLatch latch = new CountDownLatch(2);
+ mService.createNotificationChannelGroup(
+ PKG, mUid, new NotificationChannelGroup("group", "group"), true, false);
+ new Thread(() -> {
+ NotificationChannel notificationChannel = new NotificationChannel("id", "id",
+ NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setGroup("group");
+ ParceledListSlice<NotificationChannel> pls =
+ new ParceledListSlice(ImmutableList.of(notificationChannel));
+ try {
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ latch.countDown();
+ }).start();
+ new Thread(() -> {
+ try {
+ synchronized (this) {
+ wait(5000);
+ }
+ mService.createNotificationChannelGroup(PKG, mUid,
+ new NotificationChannelGroup("new", "new group"), true, false);
+ NotificationChannel notificationChannel =
+ new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setGroup("new");
+ ParceledListSlice<NotificationChannel> pls =
+ new ParceledListSlice(ImmutableList.of(notificationChannel));
+ try {
+ mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ latch.countDown();
+ }).start();
+
+ latch.await();
+ verify(mAmi).hasForegroundServiceNotification(anyString(), anyInt(), anyString());
+ }
+
+ @Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
@@ -3872,6 +3907,40 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testTextChangedSet_forNewNotifs() throws Exception {
+ NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
+ mService.addEnqueuedNotification(original);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(original.getKey());
+ runnable.run();
+ waitForIdle();
+
+ assertTrue(original.isTextChanged());
+ }
+
+ @Test
+ public void testVisuallyInterruptive_notSeen() throws Exception {
+ NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
+ mService.addNotification(original);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, original.getSbn().getId(),
+ original.getSbn().getTag(), mUid, 0,
+ new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("new title").build(),
+ UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ mService.addEnqueuedNotification(update);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(update.getKey());
+ runnable.run();
+ waitForIdle();
+
+ assertFalse(update.isInterruptive());
+ }
+
+ @Test
public void testApplyAdjustmentMultiUser() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(r);
@@ -6008,7 +6077,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testNASSettingUpgrade_userSetNull_noOnBoarding() throws RemoteException {
+ public void testNASSettingUpgrade_userSetNull() throws RemoteException {
ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component1");
TestableNotificationManagerService service = spy(mService);
int userId = 11;
@@ -6021,14 +6090,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(new ArrayList<>());
when(mAssistants.hasUserSet(userId)).thenReturn(true);
- service.migrateDefaultNASShowNotificationIfNecessary();
+ service.migrateDefaultNAS();
assertTrue(service.isNASMigrationDone(userId));
- verify(service, times(0)).createNASUpgradeNotification(eq(userId));
verify(mAssistants, times(1)).clearDefaults();
}
@Test
- public void testNASSettingUpgrade_userSetSameDefault_noOnBoarding() throws RemoteException {
+ public void testNASSettingUpgrade_userSet() throws RemoteException {
ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component1");
TestableNotificationManagerService service = spy(mService);
int userId = 11;
@@ -6041,55 +6109,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(new ArrayList(Arrays.asList(defaultComponent)));
when(mAssistants.hasUserSet(userId)).thenReturn(true);
- service.migrateDefaultNASShowNotificationIfNecessary();
- assertTrue(service.isNASMigrationDone(userId));
- verify(service, times(0)).createNASUpgradeNotification(eq(userId));
- verify(mAssistants, times(1)).resetDefaultFromConfig();
- }
-
- @Test
- public void testNASSettingUpgrade_userSetDifferentDefault_showOnboarding()
- throws RemoteException {
- ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1");
- ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2");
- TestableNotificationManagerService service = spy(mService);
- int userId = 11;
- setUsers(new int[]{userId});
- setNASMigrationDone(false, userId);
- when(mAssistants.getDefaultComponents())
- .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent)));
- when(mAssistants.getDefaultFromConfig())
- .thenReturn(newDefaultComponent);
- when(mAssistants.getAllowedComponents(anyInt()))
- .thenReturn(Arrays.asList(oldDefaultComponent));
- when(mAssistants.hasUserSet(userId)).thenReturn(true);
-
- service.migrateDefaultNASShowNotificationIfNecessary();
- assertFalse(service.isNASMigrationDone(userId));
- //TODO(b/192450820)
- //verify(service, times(1)).createNASUpgradeNotification(eq(userId));
- verify(mAssistants, times(0)).resetDefaultFromConfig();
-
- //Test user clear data before enable/disable from onboarding notification
- ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners =
- generateResetComponentValues();
- when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners);
- ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
- changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent)));
- changes.put(false, new ArrayList());
- when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes);
-
- //Clear data
- service.getBinderService().clearData("package", userId, false);
- //Test migrate flow again
- service.migrateDefaultNASShowNotificationIfNecessary();
-
- //The notification should be still there
- assertFalse(service.isNASMigrationDone(userId));
- //TODO(b/192450820)
- //verify(service, times(2)).createNASUpgradeNotification(eq(userId));
- verify(mAssistants, times(0)).resetDefaultFromConfig();
- assertEquals(oldDefaultComponent, service.getApprovedAssistant(userId));
+ service.migrateDefaultNAS();
+ verify(mAssistants, times(1)).setUserSet(userId, false);
+ //resetDefaultAssistantsIfNecessary should invoke from readPolicyXml() and migration
+ verify(mAssistants, times(2)).resetDefaultAssistantsIfNecessary();
}
@Test
@@ -6109,24 +6132,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent)));
when(mAssistants.getDefaultFromConfig())
.thenReturn(newDefaultComponent);
- //User1: need onboarding
+ //User1: set different NAS
when(mAssistants.getAllowedComponents(userId1))
.thenReturn(Arrays.asList(oldDefaultComponent));
- //User2: no onboarding
+ //User2: set to none
when(mAssistants.getAllowedComponents(userId2))
- .thenReturn(Arrays.asList(newDefaultComponent));
+ .thenReturn(new ArrayList<>());
when(mAssistants.hasUserSet(userId1)).thenReturn(true);
when(mAssistants.hasUserSet(userId2)).thenReturn(true);
- service.migrateDefaultNASShowNotificationIfNecessary();
- assertFalse(service.isNASMigrationDone(userId1));
+ service.migrateDefaultNAS();
+ // user1's setting get reset
+ verify(mAssistants, times(1)).setUserSet(userId1, false);
+ verify(mAssistants, times(0)).setUserSet(eq(userId2), anyBoolean());
assertTrue(service.isNASMigrationDone(userId2));
- //TODO(b/192450820)
- //verify(service, times(1)).createNASUpgradeNotification(any(Integer.class));
- // only user2's default get updated
- verify(mAssistants, times(1)).resetDefaultFromConfig();
}
@Test
@@ -6146,7 +6167,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent)));
when(mAssistants.getDefaultFromConfig())
.thenReturn(newDefaultComponent);
- //Both profiles: need onboarding
+ //Both profiles: set different NAS
when(mAssistants.getAllowedComponents(userId1))
.thenReturn(Arrays.asList(oldDefaultComponent));
when(mAssistants.getAllowedComponents(userId2))
@@ -6155,13 +6176,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.hasUserSet(userId1)).thenReturn(true);
when(mAssistants.hasUserSet(userId2)).thenReturn(true);
- service.migrateDefaultNASShowNotificationIfNecessary();
+ service.migrateDefaultNAS();
assertFalse(service.isNASMigrationDone(userId1));
assertFalse(service.isNASMigrationDone(userId2));
-
- // TODO(b/192450820): only user1 get notification
- //verify(service, times(1)).createNASUpgradeNotification(eq(userId1));
- //verify(service, times(0)).createNASUpgradeNotification(eq(userId2));
}
@@ -6189,79 +6206,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
//Clear data
service.getBinderService().clearData("package", userId, false);
//Test migrate flow again
- service.migrateDefaultNASShowNotificationIfNecessary();
-
- //TODO(b/192450820): The notification should not appear again
- //verify(service, times(0)).createNASUpgradeNotification(eq(userId));
- verify(mAssistants, times(0)).resetDefaultFromConfig();
- }
-
- @Test
- public void testNASUpgradeNotificationDisableBroadcast_multiProfile() {
- int userId1 = 11;
- int userId2 = 12;
- setUsers(new int[]{userId1, userId2});
- when(mUm.isManagedProfile(userId2)).thenReturn(true);
- when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2});
-
- TestableNotificationManagerService service = spy(mService);
- setNASMigrationDone(false, userId1);
- setNASMigrationDone(false, userId2);
-
- simulateNASUpgradeBroadcast(ACTION_DISABLE_NAS, userId1);
-
- assertTrue(service.isNASMigrationDone(userId1));
- assertTrue(service.isNASMigrationDone(userId2));
- // User disabled the NAS from notification, the default stored in xml should be null
- // rather than the new default
- verify(mAssistants, times(1)).clearDefaults();
- verify(mAssistants, times(0)).resetDefaultFromConfig();
-
- //TODO(b/192450820):No more notification after disabled
- //service.migrateDefaultNASShowNotificationIfNecessary();
- //verify(service, times(0)).createNASUpgradeNotification(anyInt());
- }
-
- @Test
- public void testNASUpgradeNotificationEnableBroadcast_multiUser() {
- int userId1 = 11;
- int userId2 = 12;
- setUsers(new int[]{userId1, userId2});
- when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1});
-
- TestableNotificationManagerService service = spy(mService);
- setNASMigrationDone(false, userId1);
- setNASMigrationDone(false, userId2);
+ service.migrateDefaultNAS();
- simulateNASUpgradeBroadcast(ACTION_ENABLE_NAS, userId1);
-
- assertTrue(service.isNASMigrationDone(userId1));
- assertFalse(service.isNASMigrationDone(userId2));
- verify(mAssistants, times(1)).resetDefaultFromConfig();
+ //Migration should not happen again
+ verify(mAssistants, times(0)).setUserSet(userId, false);
+ verify(mAssistants, times(0)).clearDefaults();
+ //resetDefaultAssistantsIfNecessary should only invoke once from readPolicyXml()
+ verify(mAssistants, times(1)).resetDefaultAssistantsIfNecessary();
- //TODO(b/192450820)
- //service.migrateDefaultNASShowNotificationIfNecessary();
- //verify(service, times(0)).createNASUpgradeNotification(eq(userId1));
}
- @Test
- public void testNASUpgradeNotificationLearnMoreBroadcast() {
- int userId = 11;
- setUsers(new int[]{userId});
- TestableNotificationManagerService service = spy(mService);
- setNASMigrationDone(false, userId);
- doNothing().when(mContext).startActivity(any());
-
- simulateNASUpgradeBroadcast(ACTION_LEARNMORE_NAS, userId);
-
- verify(mContext, times(1)).startActivity(any(Intent.class));
- assertFalse(service.isNASMigrationDone(userId));
- //TODO(b/192450820)
- //verify(service, times(0)).createNASUpgradeNotification(eq(userId));
- verify(mAssistants, times(0)).resetDefaultFromConfig();
- }
-
-
private void setNASMigrationDone(boolean done, int userId) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.NAS_SETTINGS_UPDATED, done ? 1 : 0, userId);
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 66d157708332..77612b9d8cef 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2224,6 +2224,19 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ public void testIsGroupBlocked_appCannotCreateAsBlocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
+ assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+
+ NotificationChannelGroup group3 = group.clone();
+ group3.setBlocked(false);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group3, true);
+ assertFalse(mHelper.isGroupBlocked(PKG_N_MR1, UID_N_MR1, group.getId()));
+ }
+
+ @Test
public void testIsGroup_appCannotResetBlock() throws Exception {
NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, group, true);
@@ -3615,7 +3628,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testGetConversations_noDisabledGroups() {
NotificationChannelGroup group = new NotificationChannelGroup("a", "a");
group.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true);
+ mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, false);
NotificationChannel parent = new NotificationChannel("parent", "p", 1);
mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
index c77a474e032c..8bc0c6c65fa3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/VibratorHelperTest.java
@@ -40,7 +40,10 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class VibratorHelperTest extends UiServiceTestCase {
+ // OFF/ON vibration pattern
private static final long[] CUSTOM_PATTERN = new long[] { 100, 200, 300, 400 };
+ // (amplitude, frequency, duration) triples list
+ private static final float[] PWLE_PATTERN = new float[] { 1, 0, 100 };
@Mock private Vibrator mVibrator;
@@ -58,12 +61,16 @@ public class VibratorHelperTest extends UiServiceTestCase {
public void createWaveformVibration_insistent_createsRepeatingVibration() {
assertRepeatingVibration(
VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ true));
+ assertRepeatingVibration(
+ VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ true));
}
@Test
public void createWaveformVibration_nonInsistent_createsSingleShotVibration() {
assertSingleVibration(
VibratorHelper.createWaveformVibration(CUSTOM_PATTERN, /* insistent= */ false));
+ assertSingleVibration(
+ VibratorHelper.createPwleWaveformVibration(PWLE_PATTERN, /* insistent= */ false));
}
@Test
@@ -71,6 +78,11 @@ public class VibratorHelperTest extends UiServiceTestCase {
assertNull(VibratorHelper.createWaveformVibration(null, false));
assertNull(VibratorHelper.createWaveformVibration(new long[0], false));
assertNull(VibratorHelper.createWaveformVibration(new long[] { 0, 0 }, false));
+
+ assertNull(VibratorHelper.createPwleWaveformVibration(null, false));
+ assertNull(VibratorHelper.createPwleWaveformVibration(new float[0], false));
+ assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0 }, false));
+ assertNull(VibratorHelper.createPwleWaveformVibration(new float[] { 0, 0, 0 }, false));
}
@Test
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 32a4774ca4f2..d4d8b86850c6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -27,6 +27,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor
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.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
@@ -44,6 +45,7 @@ import android.os.IBinder;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
+import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -69,6 +71,7 @@ import java.util.function.ToIntFunction;
@Presubmit
@RunWith(WindowTestRunner.class)
public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
+ private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
private ActivityMetricsLogger mActivityMetricsLogger;
private ActivityMetricsLogger.LaunchingState mLaunchingState;
private ActivityMetricsLaunchObserver mLaunchObserver;
@@ -136,7 +139,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
// messages that are waiting for the lock.
waitHandlerIdle(mAtm.mH);
// AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
- return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
+ return verify(mock, timeout(TIMEOUT_MS));
}
private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
@@ -257,15 +260,40 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
@Test
public void testOnActivityLaunchWhileSleeping() {
- notifyActivityLaunching(mTopActivity.intent);
- notifyActivityLaunched(START_SUCCESS, mTopActivity);
- doReturn(true).when(mTopActivity.mDisplayContent).isSleeping();
- mTopActivity.setState(Task.ActivityState.RESUMED, "test");
- mTopActivity.setVisibility(false);
+ notifyActivityLaunching(mTrampolineActivity.intent);
+ notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
+ doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping();
+ mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test");
+ mTrampolineActivity.setVisibility(false);
waitHandlerIdle(mAtm.mH);
// Not cancel immediately because in one of real cases, the keyguard may be going away or
// occluded later, then the activity can be drawn.
- verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTopActivity));
+ verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+
+ clearInvocations(mLaunchObserver);
+ mLaunchTopByTrampoline = true;
+ mTopActivity.mVisibleRequested = false;
+ notifyActivityLaunching(mTopActivity.intent);
+ // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
+ // the launch event is still valid.
+ notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
+ // The posted message will acquire wm lock, so the test needs to release the lock to verify.
+ final Throwable error = awaitInWmLock(() -> {
+ try {
+ // Though the aborting target should be eqProto(mTopActivity), use any() to avoid
+ // any changes in proto that may cause failure by different arguments.
+ verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(any());
+ } catch (Throwable e) {
+ // Catch any errors including assertion because this runs in another thread.
+ return e;
+ }
+ return null;
+ });
+ // The launch event must be cancelled because the activity keeps invisible.
+ if (error != null) {
+ throw new AssertionError(error);
+ }
}
@Test
@@ -376,6 +404,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
// Another round without setting visibility of the trampoline activity.
onActivityLaunchedTrampoline();
+ mTrampolineActivity.setState(ActivityRecord.State.PAUSING, "test");
notifyWindowsDrawn(mTopActivity);
// If the transition can start, the invisible activities should be discarded and the launch
// event be reported successfully.
@@ -447,10 +476,21 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
}
@Test
+ public void testConsecutiveLaunch() {
+ onActivityLaunched(mTrampolineActivity);
+ mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
+ mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
+ notifyActivityLaunched(START_SUCCESS, mTopActivity);
+ transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
+ }
+
+ @Test
public void testConsecutiveLaunchNewTask() {
final IBinder launchCookie = mock(IBinder.class);
+ final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
mTrampolineActivity.noDisplay = true;
mTrampolineActivity.mLaunchCookie = launchCookie;
+ mTrampolineActivity.mLaunchRootTask = launchRootTask;
onActivityLaunched(mTrampolineActivity);
final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm)
.setCreateTask(true)
@@ -464,6 +504,10 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
mTrampolineActivity.mLaunchCookie).isNull();
assertWithMessage("The last launch task has the transferred cookie").that(
activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie);
+ assertWithMessage("Trampoline's launch root task must be transferred").that(
+ mTrampolineActivity.mLaunchRootTask).isNull();
+ assertWithMessage("The last launch task has the transferred launch root task").that(
+ activityOnNewTask.mLaunchRootTask).isEqualTo(launchRootTask);
}
@Test
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 b4fbf5fe40eb..184ea521e828 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -110,18 +110,20 @@ public class ActivityOptionsTest {
@Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.setVisibility(leash, true /* visible */).apply();
+ t.show(leash).apply();
}
int cookieIndex = -1;
if (trampoline.equals(taskInfo.baseActivity)) {
cookieIndex = 0;
} else if (main.equals(taskInfo.baseActivity)) {
cookieIndex = 1;
- mainLatch.countDown();
}
if (cookieIndex >= 0) {
appearedCookies[cookieIndex] = taskInfo.launchCookies.isEmpty()
? null : taskInfo.launchCookies.get(0);
+ if (cookieIndex == 1) {
+ mainLatch.countDown();
+ }
}
}
};
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 6f04f176afd8..b770b3e3a55b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -16,8 +16,10 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -37,6 +39,7 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.InsetsState.ITYPE_IME;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -67,22 +70,21 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.INITIALIZING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STARTED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+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.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+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;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
import static com.google.common.truth.Truth.assertThat;
@@ -96,11 +98,11 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import android.app.ActivityOptions;
-import android.app.WindowConfiguration;
import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.DestroyActivityItem;
@@ -127,6 +129,9 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner.Stub;
import android.view.IWindowManager;
import android.view.IWindowSession;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
@@ -137,15 +142,18 @@ import android.window.TaskSnapshot;
import androidx.test.filters.MediumTest;
import com.android.internal.R;
-import com.android.server.wm.Task.ActivityState;
+import com.android.server.wm.ActivityRecord.State;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
/**
@@ -164,6 +172,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Before
public void setUp() throws Exception {
setBooted(mAtm);
+ // Because the booted state is set, avoid starting real home if there is no task.
+ doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any());
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
@@ -172,25 +182,25 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- public void testStackCleanupOnClearingTask() {
+ public void testTaskFragmentCleanupOnClearingTask() {
final ActivityRecord activity = createActivityWith2LevelTask();
final Task task = activity.getTask();
- final Task rootTask = activity.getRootTask();
+ final TaskFragment taskFragment = activity.getTaskFragment();
activity.onParentChanged(null /*newParent*/, task);
- verify(rootTask, times(1)).cleanUpActivityReferences(any());
+ verify(taskFragment).cleanUpActivityReferences(any());
}
@Test
- public void testStackCleanupOnActivityRemoval() {
+ public void testTaskFragmentCleanupOnActivityRemoval() {
final ActivityRecord activity = createActivityWith2LevelTask();
final Task task = activity.getTask();
- final Task rootTask = activity.getRootTask();
+ final TaskFragment taskFragment = activity.getTaskFragment();
task.removeChild(activity);
- verify(rootTask, times(1)).cleanUpActivityReferences(any());
+ verify(taskFragment).cleanUpActivityReferences(any());
}
@Test
- public void testStackCleanupOnTaskRemoval() {
+ public void testRootTaskCleanupOnTaskRemoval() {
final ActivityRecord activity = createActivityWith2LevelTask();
final Task task = activity.getTask();
final Task rootTask = activity.getRootTask();
@@ -216,7 +226,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testNoCleanupMovingActivityInSameStack() {
final ActivityRecord activity = createActivityWith2LevelTask();
final Task rootTask = activity.getRootTask();
- final Task newTask = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask).build();
+ final Task newTask = createTaskInRootTask(rootTask, 0 /* userId */);
activity.reparent(newTask, 0, null /*reason*/);
verify(rootTask, times(0)).cleanUpActivityReferences(any());
}
@@ -345,7 +355,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetsRelaunchReason_NotDragResizing() {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.RESUMED, "Testing");
+ activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -370,7 +380,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetsRelaunchReason_DragResizing() {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.RESUMED, "Testing");
+ activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -397,7 +407,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testRelaunchClearTopWaitingTranslucent() {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.RESUMED, "Testing");
+ activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -418,7 +428,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetsRelaunchReason_NonResizeConfigChanges() {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.RESUMED, "Testing");
+ activity.setState(RESUMED, "Testing");
task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
@@ -466,7 +476,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.setCreateTask(true)
.setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
.build();
- activity.setState(Task.ActivityState.RESUMED, "Testing");
+ activity.setState(RESUMED, "Testing");
activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
activity.getConfiguration()));
@@ -556,7 +566,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWith2LevelTask();
final Task task = activity.getTask();
final Task rootTask = activity.getRootTask();
- rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ rootTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final Rect stableRect = new Rect();
rootTask.mDisplayContent.getStableRect(stableRect);
@@ -598,19 +608,22 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void respectRequestedOrientationForNonResizableInSplitWindows() {
- final Task task = new TaskBuilder(mSupervisor)
- .setCreateParentTask(true).setCreateActivity(true).build();
- final Task rootTask = task.getRootTask();
+ final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
+ spyOn(tda);
+ doReturn(true).when(tda).supportsNonResizableMultiWindow();
+ final Task rootTask = mDisplayContent.getDefaultTaskDisplayArea().createRootTask(
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ rootTask.setBounds(0, 0, 1000, 500);
final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setParentTask(task)
+ .setParentTask(rootTask)
+ .setCreateTask(true)
.setOnTop(true)
.setResizeMode(RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
.build();
+ final Task task = activity.getTask();
// Task in landscape.
- rootTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- task.setBounds(0, 0, 1000, 500);
assertEquals(ORIENTATION_LANDSCAPE, task.getConfiguration().orientation);
// Asserts fixed orientation request is respected, and the orientation is not changed.
@@ -619,7 +632,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Clear size compat.
activity.clearSizeCompatMode();
activity.ensureActivityConfiguration(0 /* globalChanges */, false /* preserveWindow */);
- activity.mDisplayContent.sendNewConfiguration();
+ mDisplayContent.sendNewConfiguration();
// Relaunching the app should still respect the orientation request.
assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
@@ -629,7 +642,7 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testShouldMakeActive_deferredResume() {
final ActivityRecord activity = createActivityWithTask();
- activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setState(STOPPED, "Testing");
mSupervisor.beginDeferResume();
assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
@@ -645,7 +658,7 @@ public class ActivityRecordTests extends WindowTestsBase {
ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
finishingActivity.finishing = true;
ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
- activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setState(STOPPED, "Testing");
assertEquals(false, activity.shouldMakeActive(null /* activeActivity */));
}
@@ -654,15 +667,16 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testShouldResume_stackVisibility() {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setState(STOPPED, "Testing");
- doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+ doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE).when(task).getVisibility(null);
assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
- doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(task).getVisibility(null);
+ doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT)
+ .when(task).getVisibility(null);
assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
- doReturn(TASK_VISIBILITY_INVISIBLE).when(task).getVisibility(null);
+ doReturn(TASK_FRAGMENT_VISIBILITY_INVISIBLE).when(task).getVisibility(null);
assertEquals(false, activity.shouldResumeActivity(null /* activeActivity */));
}
@@ -670,13 +684,13 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testShouldResumeOrPauseWithResults() {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setState(STOPPED, "Testing");
ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
activity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
topActivity.finishing = true;
- doReturn(TASK_VISIBILITY_VISIBLE).when(task).getVisibility(null);
+ doReturn(TASK_FRAGMENT_VISIBILITY_VISIBLE).when(task).getVisibility(null);
assertEquals(true, activity.shouldResumeActivity(null /* activeActivity */));
assertEquals(false, activity.shouldPauseActivity(null /*activeActivity */));
}
@@ -689,7 +703,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
.build();
final Task task = activity.getTask();
- activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setState(STOPPED, "Testing");
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
try {
@@ -732,7 +746,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = createActivityWithTask();
ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(activity.getTask()).build();
topActivity.setOccludesParent(false);
- activity.setState(Task.ActivityState.STOPPED, "Testing");
+ activity.setState(STOPPED, "Testing");
activity.setVisibility(true);
activity.makeActiveIfNeeded(null /* activeActivity */);
assertEquals(STARTED, activity.getState());
@@ -1016,8 +1030,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testFinishActivityIfPossible_nonResumedFinishCompletesImmediately() {
final ActivityRecord activity = createActivityWithTask();
- final ActivityState[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
- for (ActivityState state : states) {
+ final State[] states = {INITIALIZING, STARTED, PAUSED, STOPPING, STOPPED};
+ for (State state : states) {
activity.finishing = false;
activity.setState(state, "test");
reset(activity);
@@ -1080,6 +1094,7 @@ public class ActivityRecordTests extends WindowTestsBase {
*/
@Test
public void testFinishActivityIfPossible_nonVisibleNoAppTransition() {
+ registerTestTransitionPlayer();
final ActivityRecord activity = createActivityWithTask();
// Put an activity on top of test activity to make it invisible and prevent us from
// accidentally resuming the topmost one again.
@@ -1090,6 +1105,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.finishIfPossible("test", false /* oomAdj */);
verify(activity.mDisplayContent, never()).prepareAppTransition(eq(TRANSIT_CLOSE));
+ assertFalse(activity.inTransition());
}
/**
@@ -1098,11 +1114,7 @@ public class ActivityRecordTests extends WindowTestsBase {
*/
@Test
public void testFinishActivityIfPossible_lastInTaskRequestsTransitionWithTrigger() {
- // Set-up mock shell transitions
- final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
- mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
- mAtm.getTransitionController().registerTransitionPlayer(testPlayer);
-
+ final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
final ActivityRecord activity = createActivityWithTask();
activity.finishing = false;
activity.mVisibleRequested = true;
@@ -1114,6 +1126,29 @@ public class ActivityRecordTests extends WindowTestsBase {
}
/**
+ * Verify that when collecting activity to the existing close transition, it should not affect
+ * ready state.
+ */
+ @Test
+ public void testFinishActivityIfPossible_collectToExistingTransition() {
+ final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
+ final ActivityRecord activity = createActivityWithTask();
+ activity.setState(PAUSED, "test");
+ activity.finishIfPossible("test", false /* oomAdj */);
+ final Transition lastTransition = testPlayer.mLastTransit;
+ assertTrue(lastTransition.allReady());
+ assertTrue(activity.inTransition());
+
+ // Collect another activity to the existing transition without changing ready state.
+ final ActivityRecord activity2 = createActivityRecord(activity.getTask());
+ activity2.setState(PAUSING, "test");
+ activity2.finishIfPossible("test", false /* oomAdj */);
+ assertTrue(activity2.inTransition());
+ assertEquals(lastTransition, testPlayer.mLastTransit);
+ assertTrue(lastTransition.allReady());
+ }
+
+ /**
* Verify that complete finish request for non-finishing activity is invalid.
*/
@Test(expected = IllegalArgumentException.class)
@@ -1152,7 +1187,7 @@ public class ActivityRecordTests extends WindowTestsBase {
/**
* Verify that finish request won't change the state of next top activity if the current
* finishing activity doesn't need to be destroyed immediately. The case is usually like
- * from {@link ActivityStack#completePauseLocked(boolean, ActivityRecord)} to
+ * from {@link Task#completePause(boolean, ActivityRecord)} to
* {@link ActivityRecord#completeFinishing(String)}, so the complete-pause should take the
* responsibility to resume the next activity with updating the state.
*/
@@ -1398,7 +1433,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
private void testCompleteFinishing_ensureActivitiesVisible(boolean diffTask,
- ActivityState secondActivityState) {
+ State secondActivityState) {
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1450,7 +1485,8 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testDestroyIfPossible() {
final ActivityRecord activity = createActivityWithTask();
- doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities();
+ doReturn(false).when(mRootWindowContainer)
+ .resumeFocusedTasksTopActivities();
activity.destroyIfPossible("test");
assertEquals(DESTROYING, activity.getState());
@@ -1472,7 +1508,8 @@ public class ActivityRecordTests extends WindowTestsBase {
homeStack.removeChild(t, "test");
}, true /* traverseTopToBottom */);
activity.finishing = true;
- doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities();
+ doReturn(false).when(mRootWindowContainer)
+ .resumeFocusedTasksTopActivities();
// Try to destroy the last activity above the home stack.
activity.destroyIfPossible("test");
@@ -1599,16 +1636,23 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- public void testRemoveImmediately() throws RemoteException {
- final ActivityRecord activity = createActivityWithTask();
- final WindowProcessController wpc = activity.app;
- activity.getTask().removeImmediately("test");
-
- verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken),
- isA(DestroyActivityItem.class));
- assertNull(activity.app);
- assertEquals(DESTROYED, activity.getState());
- assertFalse(wpc.hasActivities());
+ public void testRemoveImmediately() {
+ final Consumer<Consumer<ActivityRecord>> test = setup -> {
+ final ActivityRecord activity = createActivityWithTask();
+ final WindowProcessController wpc = activity.app;
+ setup.accept(activity);
+ activity.getTask().removeImmediately("test");
+ try {
+ verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), eq(activity.appToken),
+ isA(DestroyActivityItem.class));
+ } catch (RemoteException ignored) {
+ }
+ assertNull(activity.app);
+ assertEquals(DESTROYED, activity.getState());
+ assertFalse(wpc.hasActivities());
+ };
+ test.accept(activity -> activity.setState(RESUMED, "test"));
+ test.accept(activity -> activity.finishing = true);
}
@Test
@@ -1852,7 +1896,7 @@ public class ActivityRecordTests extends WindowTestsBase {
doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibility */, any() /* outInputChannel */,
+ any() /* requestedVisibilities */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */);
mAtm.mWindowManager.mStartingSurfaceController
.createTaskSnapshotSurface(activity, snapshot);
@@ -1908,8 +1952,7 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(wpc.registeredForActivityConfigChanges());
// Create a new task with custom config to reparent the activity to.
- final Task newTask =
- new TaskBuilder(mSupervisor).setParentTask(initialTask.getRootTask()).build();
+ final Task newTask = new TaskBuilder(mSupervisor).build();
final Configuration newConfig = newTask.getConfiguration();
newConfig.densityDpi += 100;
newTask.onRequestedOverrideConfigurationChanged(newConfig);
@@ -1941,8 +1984,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.diff(wpc.getRequestedOverrideConfiguration()));
// Create a new task with custom config to reparent the second activity to.
- final Task newTask =
- new TaskBuilder(mSupervisor).setParentTask(initialTask.getRootTask()).build();
+ final Task newTask = new TaskBuilder(mSupervisor).build();
final Configuration newConfig = newTask.getConfiguration();
newConfig.densityDpi += 100;
newTask.onRequestedOverrideConfigurationChanged(newConfig);
@@ -2152,7 +2194,7 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(activity.supportsPictureInPicture());
}
- private void verifyProcessInfoUpdate(ActivityRecord activity, ActivityState state,
+ private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
boolean shouldUpdate, boolean activityChange) {
reset(activity.app);
activity.setState(state, "test");
@@ -2499,16 +2541,19 @@ public class ActivityRecordTests extends WindowTestsBase {
@Test
public void testTransferStartingWindow() {
registerTestStartingWindowOrganizer();
- final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
- final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
activity1.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false, false);
waitUntilHandlersIdle();
activity2.addStartingWindow(mPackageName,
- android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1.appToken.asBinder(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1,
true, true, false, true, false, false);
waitUntilHandlersIdle();
+ assertFalse(mDisplayContent.mSkipAppTransitionAnimation);
assertNoStartingWindow(activity1);
assertHasStartingWindow(activity2);
}
@@ -2523,7 +2568,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Surprise, ...! Transfer window in the middle of the creation flow.
activity2.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0,
- activity1.appToken.asBinder(), true, true, false,
+ activity1, true, true, false,
true, false, false);
});
activity1.addStartingWindow(mPackageName,
@@ -2544,7 +2589,7 @@ public class ActivityRecordTests extends WindowTestsBase {
false, false);
waitUntilHandlersIdle();
activity2.addStartingWindow(mPackageName,
- android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1.appToken.asBinder(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1,
true, true, false, true, false, false);
waitUntilHandlersIdle();
assertNoStartingWindow(activity1);
@@ -2590,17 +2635,17 @@ public class ActivityRecordTests extends WindowTestsBase {
false /* activityCreate */, false /* suggestEmpty */);
waitUntilHandlersIdle();
assertHasStartingWindow(activity);
- activity.mStartingWindowState = ActivityRecord.STARTING_WINDOW_SHOWN;
doCallRealMethod().when(task).startActivityLocked(
any(), any(), anyBoolean(), anyBoolean(), any(), any());
// In normal case, resumeFocusedTasksTopActivities() should be called after
// startActivityLocked(). So skip resumeFocusedTasksTopActivities() in ActivityBuilder.
- doReturn(false).when(mRootWindowContainer).resumeFocusedTasksTopActivities();
+ doReturn(false).when(mRootWindowContainer)
+ .resumeFocusedTasksTopActivities();
// Make mVisibleSetFromTransferredStartingWindow true.
final ActivityRecord middle = new ActivityBuilder(mAtm).setTask(task).build();
task.startActivityLocked(middle, null /* focusedTopActivity */,
- false /* newTask */, false /* keepCurTransition */, null /* options */,
+ false /* newTask */, false /* isTaskSwitch */, null /* options */,
null /* sourceRecord */);
middle.makeFinishingLocked();
@@ -2613,9 +2658,10 @@ public class ActivityRecordTests extends WindowTestsBase {
top.setVisible(false);
// The finishing middle should be able to transfer starting window to top.
task.startActivityLocked(top, null /* focusedTopActivity */,
- false /* newTask */, false /* keepCurTransition */, null /* options */,
+ false /* newTask */, false /* isTaskSwitch */, null /* options */,
null /* sourceRecord */);
+ assertTrue(mDisplayContent.mSkipAppTransitionAnimation);
assertNull(middle.mStartingWindow);
assertHasStartingWindow(top);
assertTrue(top.isVisible());
@@ -2650,7 +2696,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Make sure the fixed rotation transform linked to activity2 when adding starting window
// on activity2.
topActivity.addStartingWindow(mPackageName,
- android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity.appToken.asBinder(),
+ android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity,
false, false, false, true, false, false);
waitUntilHandlersIdle();
assertTrue(topActivity.hasFixedRotationTransform());
@@ -2682,6 +2728,58 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
+ public void testStartingWindowInTaskFragment() {
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final WindowState startingWindow = createWindowState(
+ new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1);
+ activity1.addWindow(startingWindow);
+ activity1.mStartingData = mock(StartingData.class);
+ activity1.attachStartingWindow(startingWindow);
+ final Task task = activity1.getTask();
+ final Rect taskBounds = task.getBounds();
+ final int width = taskBounds.width();
+ final int height = taskBounds.height();
+ final BiConsumer<TaskFragment, Rect> fragmentSetup = (fragment, bounds) -> {
+ final Configuration config = fragment.getRequestedOverrideConfiguration();
+ config.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ config.windowConfiguration.setBounds(bounds);
+ fragment.onRequestedOverrideConfigurationChanged(config);
+ };
+
+ final TaskFragment taskFragment1 = new TaskFragment(
+ mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
+ fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
+ task.addChild(taskFragment1, POSITION_TOP);
+
+ final TaskFragment taskFragment2 = new TaskFragment(
+ mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
+ fragmentSetup.accept(taskFragment2, new Rect(width / 2, 0, width, height));
+ task.addChild(taskFragment2, POSITION_TOP);
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm)
+ .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
+ activity2.mVisibleRequested = true;
+ taskFragment2.addChild(activity2);
+ assertTrue(activity2.isResizeable());
+ activity1.reparent(taskFragment1, POSITION_TOP);
+
+ verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
+ eq(task.mSurfaceControl));
+ assertEquals(activity1.mStartingData, startingWindow.mStartingData);
+ assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
+ assertEquals(task, activity1.mStartingData.mAssociatedTask);
+ assertEquals(taskFragment1.getBounds(), activity1.getBounds());
+ // The activity was resized by task fragment, but starting window must still cover the task.
+ assertEquals(taskBounds, activity1.mStartingWindow.getBounds());
+
+ // The starting window is only removed when all embedded activities are drawn.
+ final WindowState activityWindow = mock(WindowState.class);
+ activity1.onFirstWindowDrawn(activityWindow);
+ assertNotNull(activity1.mStartingWindow);
+ activity2.onFirstWindowDrawn(activityWindow);
+ assertNull(activity1.mStartingWindow);
+ }
+
+ @Test
public void testTransitionAnimationBounds() {
removeGlobalMinSizeRestriction();
final Task task = new TaskBuilder(mSupervisor)
@@ -2716,10 +2814,36 @@ public class ActivityRecordTests extends WindowTestsBase {
assertEquals(taskBounds, activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM));
assertEquals(new Point(0, 0), animationPosition);
+ }
+
+ @Test
+ public void testTransitionAnimationBounds_returnTaskFragment() {
+ removeGlobalMinSizeRestriction();
+ final Task task = new TaskBuilder(mSupervisor).setCreateParentTask(true).build();
+ final Task rootTask = task.getRootTask();
+ final TaskFragment taskFragment = createTaskFragmentWithParentTask(task,
+ false /* createEmbeddedTask */);
+ final ActivityRecord activity = taskFragment.getTopNonFinishingActivity();
+ final Rect stackBounds = new Rect(0, 0, 1000, 600);
+ final Rect taskBounds = new Rect(100, 400, 600, 800);
+ final Rect taskFragmentBounds = new Rect(100, 400, 300, 800);
+ final Rect activityBounds = new Rect(100, 400, 300, 600);
+ // Set the bounds and windowing mode to window configuration directly, otherwise the
+ // testing setups may be discarded by configuration resolving.
+ rootTask.getWindowConfiguration().setBounds(stackBounds);
+ task.getWindowConfiguration().setBounds(taskBounds);
+ taskFragment.getWindowConfiguration().setBounds(taskFragmentBounds);
+ activity.getWindowConfiguration().setBounds(activityBounds);
- // ROOT_TASK_CLIP_BEFORE_ANIM should use stack bounds since it won't be clipped later.
- task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- assertEquals(rootTask.getBounds(), activity.getAnimationBounds(ROOT_TASK_CLIP_BEFORE_ANIM));
+ // Check that anim bounds for freeform window match task fragment bounds
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(taskFragment.getBounds(), activity.getAnimationBounds(ROOT_TASK_CLIP_NONE));
+
+ // ROOT_TASK_CLIP_AFTER_ANIM should use task fragment bounds since they will be clipped by
+ // bounds animation layer.
+ task.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ assertEquals(taskFragment.getBounds(),
+ activity.getAnimationBounds(ROOT_TASK_CLIP_AFTER_ANIM));
}
@Test
@@ -2739,6 +2863,40 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
+ public void testCloseToSquareFixedOrientationPortrait() {
+ // create a square display
+ final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
+ .setSystemDecorations(true).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
+
+ // create a fixed portrait activity
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
+
+ // check that both the configuration and app bounds are portrait
+ assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
+ assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
+ <= activity.getConfiguration().windowConfiguration.getAppBounds().height());
+ }
+
+ @Test
+ public void testCloseToSquareFixedOrientationLandscape() {
+ // create a square display
+ final DisplayContent squareDisplay = new TestDisplayContent.Builder(mAtm, 2000, 2000)
+ .setSystemDecorations(true).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
+
+ // create a fixed landscape activity
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task)
+ .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE).build();
+
+ // check that both the configuration and app bounds are landscape
+ assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
+ assertTrue(activity.getConfiguration().windowConfiguration.getAppBounds().width()
+ > activity.getConfiguration().windowConfiguration.getAppBounds().height());
+ }
+
+ @Test
public void testSetVisibility_visibleToVisible() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true).build();
@@ -2866,6 +3024,7 @@ public class ActivityRecordTests extends WindowTestsBase {
mDisplayContent.setImeInputTarget(app);
// Simulate app is closing and expect the last IME is shown and IME insets is frozen.
+ mDisplayContent.mOpeningApps.clear();
app.mActivityRecord.commitVisibility(false, false);
app.mActivityRecord.onWindowsGone();
@@ -2884,6 +3043,136 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+
+ InsetsSource imeSource = new InsetsSource(ITYPE_IME);
+ app.getInsetsState().addSource(imeSource);
+ mDisplayContent.setImeLayeringTarget(app);
+ mDisplayContent.updateImeInputAndControlTarget(app);
+
+ InsetsState state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+ assertFalse(state.getSource(ITYPE_IME).isVisible());
+ assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty());
+
+ // Simulate app is closing and expect IME insets is frozen.
+ mDisplayContent.mOpeningApps.clear();
+ app.mActivityRecord.commitVisibility(false, false);
+ app.mActivityRecord.onWindowsGone();
+ assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Simulate app re-start input or turning screen off/on then unlocked by un-secure
+ // keyguard to back to the app, expect IME insets is not frozen
+ imeSource.setFrame(new Rect(100, 400, 500, 500));
+ app.getInsetsState().addSource(imeSource);
+ app.getInsetsState().setSourceVisible(ITYPE_IME, true);
+ mDisplayContent.updateImeInputAndControlTarget(app);
+ assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Verify when IME is visible and the app can receive the right IME insets from policy.
+ makeWindowVisibleAndDrawn(app, mImeWindow);
+ state = mDisplayContent.getInsetsPolicy().getInsetsForWindow(app);
+ assertTrue(state.getSource(ITYPE_IME).isVisible());
+ assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
+ }
+
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
+ @Test
+ public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest()
+ throws RemoteException {
+ final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
+ final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
+
+ mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow(
+ mImeWindow, null, null);
+ mImeWindow.getControllableInsetProvider().setServerVisible(true);
+
+ // Simulate app2 is closing and let app1 is visible to be IME targets.
+ makeWindowVisibleAndDrawn(app1, mImeWindow);
+ mDisplayContent.setImeLayeringTarget(app1);
+ mDisplayContent.updateImeInputAndControlTarget(app1);
+ app2.mActivityRecord.commitVisibility(false, false);
+
+ // app1 requests IME visible.
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_IME, true);
+ app1.setRequestedVisibilities(requestedVisibilities);
+ mDisplayContent.getInsetsStateController().onInsetsModified(app1);
+
+ // Verify app1's IME insets is visible and app2's IME insets frozen flag set.
+ assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible());
+ assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+
+ // Simulate switching to app2 to make it visible to be IME targets.
+ makeWindowVisibleAndDrawn(app2);
+ spyOn(app2);
+ spyOn(app2.mClient);
+ ArgumentCaptor<InsetsState> insetsStateCaptor = ArgumentCaptor.forClass(InsetsState.class);
+ doReturn(true).when(app2).isReadyToDispatchInsetsState();
+ mDisplayContent.setImeLayeringTarget(app2);
+ mDisplayContent.updateImeInputAndControlTarget(app2);
+
+ // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets
+ // to client if the app didn't request IME visible.
+ assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
+ verify(app2.mClient, atLeastOnce()).insetsChanged(insetsStateCaptor.capture(), anyBoolean(),
+ anyBoolean());
+ assertFalse(insetsStateCaptor.getAllValues().get(0).peekSource(ITYPE_IME).isVisible());
+ }
+
+ @Test
+ public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ makeWindowVisibleAndDrawn(app);
+
+ // Put the activity in close transition.
+ mDisplayContent.mOpeningApps.clear();
+ mDisplayContent.mClosingApps.add(app.mActivityRecord);
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+
+ // Remove window during transition, so it is requested to hide, but won't be committed until
+ // the transition is finished.
+ app.mActivityRecord.onRemovedFromDisplay();
+
+ assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord));
+ assertFalse(app.mActivityRecord.isVisibleRequested());
+ assertTrue(app.mActivityRecord.isVisible());
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Start transition.
+ app.mActivityRecord.prepareSurfaces();
+
+ // Because the app is waiting for transition, it should not hide the surface.
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+ }
+
+ @Test
+ public void testInClosingAnimation_visibilityCommitted_hideSurface() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ makeWindowVisibleAndDrawn(app);
+
+ // Put the activity in close transition.
+ mDisplayContent.mOpeningApps.clear();
+ mDisplayContent.mClosingApps.add(app.mActivityRecord);
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+
+ // Commit visibility before start transition.
+ app.mActivityRecord.commitVisibility(false, false);
+
+ assertFalse(app.mActivityRecord.isVisibleRequested());
+ assertFalse(app.mActivityRecord.isVisible());
+ assertTrue(app.mActivityRecord.isSurfaceShowing());
+
+ // Start transition.
+ app.mActivityRecord.prepareSurfaces();
+
+ // Because the app visibility has been committed before the transition start, it should hide
+ // the surface.
+ assertFalse(app.mActivityRecord.isSurfaceShowing());
+ }
+
private void assertHasStartingWindow(ActivityRecord atoken) {
assertNotNull(atoken.mStartingSurface);
assertNotNull(atoken.mStartingData);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index a3ad09a50b8c..c103bc6fb9a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -42,6 +43,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.testing.DexmakerShareClassLoaderRule;
+import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -64,7 +66,7 @@ import org.mockito.MockitoAnnotations;
* Unit tests for {@link ActivityStartInterceptorTest}.
*
* Build/Install/Run:
- * atest WmTests:ActivityStartInterceptorTest
+ * atest WmTests:ActivityStartInterceptorTest
*/
@SmallTest
@Presubmit
@@ -114,6 +116,9 @@ public class ActivityStartInterceptorTest {
private ActivityStartInterceptor mInterceptor;
private ActivityInfo mAInfo = new ActivityInfo();
+ private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks =
+ new SparseArray<>();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -157,6 +162,9 @@ public class ActivityStartInterceptorTest {
TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
.thenReturn(true);
+ // Mock the activity start callbacks
+ when(mService.getActivityInterceptorCallbacks()).thenReturn(mActivityInterceptorCallbacks);
+
// Initialise activity info
mAInfo.applicationInfo = new ApplicationInfo();
mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -285,4 +293,38 @@ public class ActivityStartInterceptorTest {
// THEN calling intercept returns false
assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
}
+
+ public void addMockInterceptorCallback(@Nullable Intent intent) {
+ int size = mActivityInterceptorCallbacks.size();
+ mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() {
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ return intent;
+ }
+ });
+ }
+
+ @Test
+ public void testInterceptionCallback_singleCallback() {
+ addMockInterceptorCallback(new Intent("android.test.foo"));
+
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
+ }
+
+ @Test
+ public void testInterceptionCallback_singleCallbackReturnsNull() {
+ addMockInterceptorCallback(null);
+
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ }
+
+ @Test
+ public void testInterceptionCallback_fallbackToSecondCallback() {
+ addMockInterceptorCallback(null);
+ addMockInterceptorCallback(new Intent("android.test.second"));
+
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertEquals("android.test.second", mInterceptor.mIntent.getAction());
+ }
}
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 18450b64c8b1..01deb5c7834c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -32,6 +32,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
@@ -479,7 +480,7 @@ public class ActivityStarterTests extends WindowTestsBase {
final ActivityRecord splitSecondActivity =
new ActivityBuilder(mAtm).setCreateTask(true).build();
final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor)
- .setParentTask(splitOrg.mPrimary)
+ .setParentTaskFragment(splitOrg.mPrimary)
.setCreateActivity(true)
.build()
.getTopMostActivity();
@@ -760,12 +761,12 @@ public class ActivityStarterTests extends WindowTestsBase {
}
/**
- * This test ensures that {@link ActivityStarter#setTargetStackAndMoveToFrontIfNeeded} will
- * move the existing task to front if the current focused stack doesn't have running task.
+ * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will
+ * move the existing task to front if the current focused root task doesn't have running task.
*/
@Test
- public void testBringTaskToFrontWhenFocusedStackIsFinising() {
- // Put 2 tasks in the same stack (simulate the behavior of home stack).
+ public void testBringTaskToFrontWhenFocusedTaskIsFinishing() {
+ // Put 2 tasks in the same root task (simulate the behavior of home root task).
final Task rootTask = new TaskBuilder(mSupervisor).build();
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setParentTask(rootTask)
@@ -782,13 +783,16 @@ public class ActivityStarterTests extends WindowTestsBase {
assertEquals(finishingTopActivity, mRootWindowContainer.topRunningActivity());
finishingTopActivity.finishing = true;
- // Launch the bottom task of the target stack.
+ // Launch the bottom task of the target root task.
prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetLaunchStack */)
- .setReason("testBringTaskToFrontWhenTopStackIsFinising")
- .setIntent(activity.intent)
+ .setReason("testBringTaskToFrontWhenFocusedTaskIsFinishing")
+ .setIntent(activity.intent.addFlags(
+ FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
.execute();
+ verify(activity.getRootTask()).startActivityLocked(any(), any(), anyBoolean(),
+ eq(true) /* isTaskSwitch */, any(), any());
// The hierarchies of the activity should move to front.
- assertEquals(activity, mRootWindowContainer.topRunningActivity());
+ assertEquals(activity.getTask(), mRootWindowContainer.topRunningActivity().getTask());
}
/**
@@ -857,7 +861,7 @@ public class ActivityStarterTests extends WindowTestsBase {
// Create another activity on top of the secondary display.
final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
- final Task topTask = new TaskBuilder(mSupervisor).setParentTask(topStack).build();
+ final Task topTask = new TaskBuilder(mSupervisor).setParentTaskFragment(topStack).build();
new ActivityBuilder(mAtm).setTask(topTask).build();
doReturn(mActivityMetricsLogger).when(mSupervisor).getActivityMetricsLogger();
@@ -921,7 +925,7 @@ public class ActivityStarterTests extends WindowTestsBase {
DEFAULT_COMPONENT_PACKAGE_NAME + ".SingleTaskActivity");
final Task task = new TaskBuilder(mSupervisor)
.setComponent(componentName)
- .setParentTask(stack)
+ .setParentTaskFragment(stack)
.build();
return new ActivityBuilder(mAtm)
.setComponent(componentName)
@@ -1057,8 +1061,8 @@ public class ActivityStarterTests extends WindowTestsBase {
final ActivityStarter starter = prepareStarter(0 /* flags */);
starter.mStartActivity = new ActivityBuilder(mAtm).build();
final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
- .setParentTask(mAtm.mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+ .setParentTaskFragment(createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD))
.setUserId(10)
.build();
@@ -1141,6 +1145,7 @@ public class ActivityStarterTests extends WindowTestsBase {
/* doResume */true,
/* options */null,
/* inTask */null,
+ /* inTaskFragment */ null,
/* restrictedBgActivity */false,
/* intentGrants */null);
@@ -1151,6 +1156,31 @@ public class ActivityStarterTests extends WindowTestsBase {
}
@Test
+ public void testStartActivityInner_inTaskFragment() {
+ final ActivityStarter starter = prepareStarter(0, false);
+ final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build();
+ final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final TaskFragment taskFragment = new TaskFragment(mAtm, sourceRecord.token,
+ true /* createdByOrganizer */);
+ sourceRecord.getTask().addChild(taskFragment, POSITION_TOP);
+
+ starter.startActivityInner(
+ /* r */targetRecord,
+ /* sourceRecord */ sourceRecord,
+ /* voiceSession */null,
+ /* voiceInteractor */ null,
+ /* startFlags */ 0,
+ /* doResume */true,
+ /* options */null,
+ /* inTask */null,
+ /* inTaskFragment */ taskFragment,
+ /* restrictedBgActivity */false,
+ /* intentGrants */null);
+
+ assertTrue(taskFragment.hasChild());
+ }
+
+ @Test
public void testLaunchCookie_newAndExistingTask() {
final ActivityStarter starter = prepareStarter(0, false);
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 2de9aba7afb3..5d1a06825a75 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -26,9 +26,16 @@ 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.verify;
+import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
+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;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
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.anyBoolean;
@@ -37,15 +44,21 @@ import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.EnterPipRequestedItem;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.IBinder;
+import android.os.LocaleList;
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -57,6 +70,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
import java.util.ArrayList;
@@ -76,6 +90,9 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
ArgumentCaptor.forClass(ClientTransaction.class);
+ private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
+ private static final int DEFAULT_USER_ID = 100;
+
@Before
public void setUp() throws Exception {
setBooted(mAtm);
@@ -247,7 +264,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
.setTask(mRootWindowContainer.getDefaultTaskDisplayArea().getOrCreateRootHomeTask())
.build();
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- activity.setState(Task.ActivityState.RESUMED, "test");
+ activity.setState(RESUMED, "test");
mSupervisor.endDeferResume();
assertEquals(activity.app, mAtm.mInternal.getTopApp());
@@ -257,13 +274,13 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
activity.mVisibleRequested = false;
activity.setVisible(false);
activity.getTask().setPausingActivity(activity);
- homeActivity.setState(Task.ActivityState.PAUSED, "test");
+ homeActivity.setState(PAUSED, "test");
// Even the visibility states are invisible, the next activity should be resumed because
// the crashed activity was pausing.
mAtm.mInternal.handleAppDied(activity.app, false /* restarting */,
null /* finishInstrumentationCallback */);
- assertEquals(Task.ActivityState.RESUMED, homeActivity.getState());
+ assertEquals(RESUMED, homeActivity.getState());
assertEquals(homeActivity.app, mAtm.mInternal.getTopApp());
}
@@ -274,7 +291,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
final Task rootHomeTask = mWm.mRoot.getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
final ActivityRecord homeActivity = new ActivityBuilder(mAtm).setTask(rootHomeTask).build();
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
- topActivity.setState(Task.ActivityState.RESUMED, "test");
+ topActivity.setState(RESUMED, "test");
final Consumer<ActivityRecord> assertTopNonSleeping = activity -> {
assertFalse(mAtm.mInternal.isSleeping());
@@ -290,18 +307,27 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
verify(mSupervisor.mGoingToSleepWakeLock).acquire();
doReturn(true).when(mSupervisor.mGoingToSleepWakeLock).isHeld();
- assertEquals(Task.ActivityState.PAUSING, topActivity.getState());
+ assertEquals(PAUSING, topActivity.getState());
assertTrue(mAtm.mInternal.isSleeping());
assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
mAtm.mInternal.getTopProcessState());
// The top app should not change while sleeping.
assertEquals(topActivity.app, mAtm.mInternal.getTopApp());
+ mAtm.startLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY
+ | ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY);
+ assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState());
+ // Because there is no unknown visibility record, the state will be restored if other
+ // reasons are all done.
+ mAtm.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+ assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
+ mAtm.mInternal.getTopProcessState());
+
// If all activities are stopped, the sleep wake lock must be released.
final Task topRootTask = topActivity.getRootTask();
doReturn(true).when(rootHomeTask).goToSleepIfPossible(anyBoolean());
doReturn(true).when(topRootTask).goToSleepIfPossible(anyBoolean());
- topActivity.setState(Task.ActivityState.STOPPING, "test");
+ topActivity.setState(STOPPING, "test");
topActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
null /* description */);
verify(mSupervisor.mGoingToSleepWakeLock).release();
@@ -476,5 +502,412 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
assertTrue(activity.supportsMultiWindow());
assertTrue(task.supportsMultiWindow());
}
-}
+ @Test
+ public void testPackageConfigUpdate_locales_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
+
+ WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_nightMode_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertTrue(wpcAfterConfigChange.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_multipleLocaleUpdates_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpc.getConfiguration().getLocales());
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("ja-XC,en-XC")).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("ja-XC,en-XC"),
+ wpc.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_multipleNightModeUpdates_successfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_NO).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_onPackageUninstall_configShouldNotApply() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ mAtm.mInternal.onPackageUninstalled(DEFAULT_PACKAGE_NAME);
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_LocalesEmptyAndNightModeUndefined_configShouldNotApply() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpc.getConfiguration().getLocales());
+
+ packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList())
+ .setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpc.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_WhenUserRemoved_configShouldNotApply() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ mAtm.mInternal.removeUser(DEFAULT_USER_ID);
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_setLocaleListToEmpty_doesNotOverlayLocaleListInWpc() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ packageConfigUpdater.setLocales(LocaleList.getEmptyLocaleList()).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_resetNightMode_doesNotOverrideNightModeInWpc() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID));
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater();
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB"))
+ .setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ WindowProcessController wpcAfterConfigChange1 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange1.getConfiguration().getLocales());
+ assertTrue(wpcAfterConfigChange1.getConfiguration().isNightModeActive());
+
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_UNDEFINED).commit();
+
+ WindowProcessController wpcAfterConfigChange2 = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange2.getConfiguration().getLocales());
+ assertFalse(wpcAfterConfigChange2.getConfiguration().isNightModeActive());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_localesNotSet_localeConfigRetrievedNull() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true,
+ DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ mAtm.mInternal.onProcessAdded(wpc);
+
+ ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
+ .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ // when no configuration is set we get a null object.
+ assertNull(appSpecificConfig);
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_ID);
+ packageConfigUpdater.setNightMode(Configuration.UI_MODE_NIGHT_YES).commit();
+
+ ActivityTaskManagerInternal.PackageConfig appSpecificConfig2 = mAtm.mInternal
+ .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertNotNull(appSpecificConfig2);
+ assertNull(appSpecificConfig2.mLocales);
+ assertEquals(appSpecificConfig2.mNightMode.intValue(), Configuration.UI_MODE_NIGHT_YES);
+ }
+
+ @Test
+ public void testPackageConfigUpdate_appNotRunning_configSuccessfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true,
+ DEFAULT_USER_ID);
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_ID);
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
+
+ // Verifies if the persisted app-specific configuration is same as the committed
+ // configuration.
+ ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
+ .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertNotNull(appSpecificConfig);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales);
+
+ // Verifies if the persisted configuration for an arbitrary app is applied correctly when
+ // a new WindowProcessController is created for it.
+ WindowProcessController wpcAfterConfigChange = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpcAfterConfigChange.getConfiguration().getLocales());
+ }
+
+ @Test
+ public void testPackageConfigUpdate_appRunning_configSuccessfullyApplied() {
+ Configuration config = mAtm.getGlobalConfiguration();
+ config.setLocales(LocaleList.forLanguageTags("en-XC"));
+ mAtm.updateGlobalConfigurationLocked(config, true, true,
+ DEFAULT_USER_ID);
+ WindowProcessController wpc = createWindowProcessController(
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ mAtm.mProcessMap.put(Binder.getCallingPid(), wpc);
+ mAtm.mInternal.onProcessAdded(wpc);
+
+ ActivityTaskManagerInternal.PackageConfigurationUpdater packageConfigUpdater =
+ mAtm.mInternal.createPackageConfigurationUpdater(DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_ID);
+
+ packageConfigUpdater.setLocales(LocaleList.forLanguageTags("en-XA,ar-XB")).commit();
+
+ ActivityTaskManagerInternal.PackageConfig appSpecificConfig = mAtm.mInternal
+ .getApplicationConfig(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+
+ // Verifies if the persisted app-specific configuration is same as the committed
+ // configuration.
+ assertNotNull(appSpecificConfig);
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB"), appSpecificConfig.mLocales);
+
+ // Verifies if the committed configuration is successfully applied to the required
+ // application while it is currently running.
+ assertEquals(LocaleList.forLanguageTags("en-XA,ar-XB,en-XC"),
+ wpc.getConfiguration().getLocales());
+ }
+
+ private WindowProcessController createWindowProcessController(String packageName,
+ int userId) {
+ WindowProcessListener mMockListener = Mockito.mock(WindowProcessListener.class);
+ ApplicationInfo info = mock(ApplicationInfo.class);
+ info.packageName = packageName;
+ WindowProcessController wpc = new WindowProcessController(
+ mAtm, info, packageName, 0, userId, null, mMockListener);
+ wpc.setThread(mock(IApplicationThread.class));
+ return wpc;
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterActivityStartInterceptor_IndexTooSmall() {
+ mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID - 1,
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ return null;
+ }
+ });
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterActivityStartInterceptor_IndexTooLarge() {
+ mAtm.mInternal.registerActivityStartInterceptor(LAST_ORDERED_ID + 1,
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ return null;
+ }
+ });
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRegisterActivityStartInterceptor_DuplicateId() {
+ mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ return null;
+ }
+ });
+ mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ return null;
+ }
+ });
+ }
+
+ @Test
+ public void testRegisterActivityStartInterceptor() {
+ assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
+
+ mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+ new ActivityInterceptorCallback() {
+ @Nullable
+ @Override
+ public Intent intercept(ActivityInterceptorInfo info) {
+ return null;
+ }
+ });
+
+ assertEquals(1, mAtm.getActivityInterceptorCallbacks().size());
+ assertTrue(mAtm.getActivityInterceptorCallbacks().contains(FIRST_ORDERED_ID));
+ }
+}
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 19f9b758811f..66da2a631868 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.START_DELIVERED_TO_TOP;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -36,6 +37,7 @@ import static org.junit.Assert.assertTrue;
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.mock;
import static org.mockito.Mockito.timeout;
@@ -52,6 +54,7 @@ import androidx.test.filters.MediumTest;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
import java.util.concurrent.TimeUnit;
@@ -111,16 +114,18 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
final ActivityMetricsLogger.LaunchingState launchingState =
new ActivityMetricsLogger.LaunchingState();
spyOn(launchingState);
- doReturn(true).when(launchingState).contains(eq(secondActivity));
+ doReturn(true).when(launchingState).hasActiveTransitionInfo();
+ doReturn(true).when(launchingState).contains(
+ ArgumentMatchers.argThat(r -> r == firstActivity || r == secondActivity));
// The test case already runs inside global lock, so above thread can only execute after
// this waiting method that releases the lock.
mSupervisor.waitActivityVisibleOrLaunched(taskToFrontWait, firstActivity, launchingState);
// Assert that the thread is finished.
assertTrue(condition.block(TIMEOUT_MS));
- assertEquals(taskToFrontWait.result, START_TASK_TO_FRONT);
- assertEquals(taskToFrontWait.who, secondActivity.mActivityComponent);
- assertEquals(taskToFrontWait.launchState, WaitResult.LAUNCH_STATE_HOT);
+ assertEquals(START_TASK_TO_FRONT, taskToFrontWait.result);
+ assertEquals(secondActivity.mActivityComponent, taskToFrontWait.who);
+ assertEquals(WaitResult.LAUNCH_STATE_HOT, taskToFrontWait.launchState);
// START_TASK_TO_FRONT means that another component will be visible, so the component
// should not be assigned as the first activity.
assertNull(launchedComponent[0]);
@@ -263,6 +268,30 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
}
/**
+ * Verifies that process state will be updated with pending top without activity state change.
+ * E.g. switch focus between resumed activities in multi-window mode.
+ */
+ @Test
+ public void testUpdatePendingTopForTopResumed() {
+ final Task task1 = new TaskBuilder(mSupervisor)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW).build();
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm)
+ .setTask(task1).setUid(ActivityBuilder.DEFAULT_FAKE_UID + 1).build();
+ task1.setResumedActivity(activity1, "test");
+
+ final ActivityRecord activity2 = new TaskBuilder(mSupervisor)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setCreateActivity(true).build().getTopMostActivity();
+ activity2.getTask().setResumedActivity(activity2, "test");
+
+ mAtm.mAmInternal.deletePendingTopUid(activity1.getUid());
+ clearInvocations(mAtm);
+ activity1.moveFocusableActivityToTop("test");
+ assertTrue(mAtm.mAmInternal.isPendingTopUid(activity1.getUid()));
+ verify(mAtm).updateOomAdj();
+ }
+
+ /**
* We need to launch home again after user unlocked for those displays that do not have
* encryption aware home app.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 3a6aac9d03d5..506270657e42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -26,16 +26,28 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
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.anyInt;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -47,8 +59,9 @@ import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -94,11 +107,24 @@ public class AppTransitionControllerTest extends WindowTestsBase {
assertEquals(WindowManager.TRANSIT_OLD_UNSET,
AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null, null, false));
+ mDisplayContent.mChangingContainers, null, null, false));
+ }
+
+ @Test
+ public void testClearTaskSkipAppExecuteTransition() {
+ final ActivityRecord behind = createActivityRecord(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final Task task = behind.getTask();
+ final ActivityRecord top = createActivityRecord(task);
+ top.setState(ActivityRecord.State.RESUMED, "test");
+ behind.setState(ActivityRecord.State.STARTED, "test");
+ behind.mVisibleRequested = true;
+
+ task.performClearTask("test");
+ assertFalse(mDisplayContent.mAppTransition.isReady());
}
@Test
- @FlakyTest(bugId = 131005232)
public void testTranslucentOpen() {
final ActivityRecord behind = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -112,12 +138,11 @@ public class AppTransitionControllerTest extends WindowTestsBase {
assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN,
AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
- mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null, null, false));
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null, null, false));
}
@Test
- @FlakyTest(bugId = 131005232)
public void testTranslucentClose() {
final ActivityRecord behind = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -129,11 +154,10 @@ public class AppTransitionControllerTest extends WindowTestsBase {
assertEquals(WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE,
AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null, null, false));
+ mDisplayContent.mChangingContainers, null, null, false));
}
@Test
- @FlakyTest(bugId = 131005232)
public void testChangeIsNotOverwritten() {
final ActivityRecord behind = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -144,14 +168,14 @@ public class AppTransitionControllerTest extends WindowTestsBase {
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mOpeningApps.add(behind);
mDisplayContent.mOpeningApps.add(translucentOpening);
+ mDisplayContent.mChangingContainers.add(translucentOpening.getTask());
assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null, null, false));
+ mDisplayContent.mChangingContainers, null, null, false));
}
@Test
- @FlakyTest(bugId = 131005232)
public void testTransitWithinTask() {
final ActivityRecord opening = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
@@ -198,7 +222,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- appWindowClosing, null, false));
+ mDisplayContent.mChangingContainers, appWindowClosing, null, false));
}
@Test
@@ -229,7 +253,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
assertEquals(WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN,
AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- appWindowClosing, null, false));
+ mDisplayContent.mChangingContainers, appWindowClosing, null, false));
}
@Test
@@ -310,6 +334,18 @@ public class AppTransitionControllerTest extends WindowTestsBase {
}
@Test
+ public void testExitAnimationDone_beforeAppTransition() {
+ final Task task = createTask(mDisplayContent);
+ final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win");
+ spyOn(win);
+ win.mAnimatingExit = true;
+ mDisplayContent.mAppTransition.setTimeout();
+ mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+
+ verify(win).onExitAnimationDone();
+ }
+
+ @Test
public void testGetAnimationTargets_windowsAreBeingReplaced() {
// [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible)
// +- [AppWindow1] (being-replaced)
@@ -375,7 +411,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
final ArraySet<ActivityRecord> closing = new ArraySet<>();
closing.add(activity3);
- // Promote animation targets to TaskStack level. Invisible ActivityRecords don't affect
+ // Promote animation targets to root Task level. Invisible ActivityRecords don't affect
// promotion decision.
assertEquals(
new ArraySet<>(new WindowContainer[]{activity1.getRootTask()}),
@@ -520,6 +556,128 @@ public class AppTransitionControllerTest extends WindowTestsBase {
opening, closing, false /* visible */));
}
+ @Test
+ public void testGetAnimationTargets_openingClosingTaskFragment() {
+ // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
+ // +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
+ final Task parentTask = createTask(mDisplayContent);
+ final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(parentTask,
+ false /* createEmbeddedTask */);
+ final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+
+ final TaskFragment taskFragment2 = createTaskFragmentWithParentTask(parentTask,
+ false /* createEmbeddedTask */);
+ final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
+ activity2.setVisible(true);
+ activity2.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Promote animation targets up to TaskFragment level, not beyond.
+ assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_openingClosingTaskFragmentWithEmbeddedTask() {
+ // [DefaultTDA] - [Task] -+- [TaskFragment1] - [ActivityRecord1] (opening, invisible)
+ // +- [TaskFragment2] - [ActivityRecord2] (closing, visible)
+ final Task parentTask = createTask(mDisplayContent);
+ final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(parentTask,
+ true /* createEmbeddedTask */);
+ final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+
+ final TaskFragment taskFragment2 = createTaskFragmentWithParentTask(parentTask,
+ true /* createEmbeddedTask */);
+ final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
+ activity2.setVisible(true);
+ activity2.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Promote animation targets up to TaskFragment level, not beyond.
+ assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(new ArraySet<>(new WindowContainer[]{taskFragment2}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_openingTheOnlyTaskFragmentInTask() {
+ // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (opening, invisible)
+ // +- [Task2] - [ActivityRecord2] (closing, visible)
+ final Task task1 = createTask(mDisplayContent);
+ final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(task1,
+ false /* createEmbeddedTask */);
+ final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+ activity1.setVisible(false);
+ activity1.mVisibleRequested = true;
+
+ final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
+ activity2.setVisible(true);
+ activity2.mVisibleRequested = false;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity1);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity2);
+
+ // Promote animation targets up to leaf Task level because there's only one TaskFragment in
+ // the Task.
+ assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
+ @Test
+ public void testGetAnimationTargets_closingTheOnlyTaskFragmentInTask() {
+ // [DefaultTDA] -+- [Task1] - [TaskFragment1] - [ActivityRecord1] (closing, visible)
+ // +- [Task2] - [ActivityRecord2] (opening, invisible)
+ final Task task1 = createTask(mDisplayContent);
+ final TaskFragment taskFragment1 = createTaskFragmentWithParentTask(task1,
+ false /* createEmbeddedTask */);
+ final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
+ activity1.setVisible(true);
+ activity1.mVisibleRequested = false;
+
+ final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
+ activity2.setVisible(false);
+ activity2.mVisibleRequested = true;
+
+ final ArraySet<ActivityRecord> opening = new ArraySet<>();
+ opening.add(activity2);
+ final ArraySet<ActivityRecord> closing = new ArraySet<>();
+ closing.add(activity1);
+
+ // Promote animation targets up to leaf Task level because there's only one TaskFragment in
+ // the Task.
+ assertEquals(new ArraySet<>(new WindowContainer[]{activity2.getTask()}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, true /* visible */));
+ assertEquals(new ArraySet<>(new WindowContainer[]{task1}),
+ AppTransitionController.getAnimationTargets(
+ opening, closing, false /* visible */));
+ }
+
static class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
@Override
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
@@ -619,4 +777,254 @@ public class AppTransitionControllerTest extends WindowTestsBase {
mAppTransitionController.getRemoteAnimationOverride(
activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
}
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithEmbeddedActivity() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ activity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+
+ // Should be overridden.
+ verify(mDisplayContent.mAppTransition)
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ final Task task = createTask(mDisplayContent);
+ // Closing non-embedded activity.
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ closingActivity.allDrawn = true;
+ // Opening TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ openingActivity.allDrawn = true;
+ task.effectiveUid = openingActivity.getUid();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+ // Should be overridden.
+ verify(mDisplayContent.mAppTransition)
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideEmbeddedActivityWithDiffUid() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ final Task task = createTask(mDisplayContent);
+ // Closing TaskFragment with embedded activity.
+ final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord closingActivity = taskFragment1.getTopMostActivity();
+ closingActivity.allDrawn = true;
+ closingActivity.info.applicationInfo.uid = 12345;
+ // Opening TaskFragment with embedded activity with different UID.
+ final TaskFragment taskFragment2 = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord openingActivity = taskFragment2.getTopMostActivity();
+ openingActivity.info.applicationInfo.uid = 54321;
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1);
+
+ // Should be overridden.
+ verify(mDisplayContent.mAppTransition)
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithTwoApps() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Closing activity in Task1.
+ final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
+ closingActivity.allDrawn = true;
+ // Opening TaskFragment with embedded activity in Task2.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition for TaskFragment.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+ // Should not be overridden.
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideNonEmbeddedActivityWithDiffUid() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ final Task task = createTask(mDisplayContent);
+ // Closing TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord closingActivity = taskFragment.getTopMostActivity();
+ closingActivity.allDrawn = true;
+ closingActivity.info.applicationInfo.uid = 12345;
+ task.effectiveUid = closingActivity.getUid();
+ // Opening non-embedded activity with different UID.
+ final ActivityRecord openingActivity = createActivityRecord(task);
+ openingActivity.info.applicationInfo.uid = 54321;
+ openingActivity.allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment);
+
+ // Should not be overridden
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithWallpaper() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new TestRemoteAnimationRunner(), 10, 1);
+ setupTaskFragmentRemoteAnimation(organizer, adapter);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(
+ createTask(mDisplayContent), organizer);
+ final ActivityRecord activity = taskFragment.getTopMostActivity();
+ activity.allDrawn = true;
+ // Set wallpaper as visible.
+ final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+ mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+ spyOn(mDisplayContent.mWallpaperController);
+ doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare a transition.
+ prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment);
+
+ // Should not be overridden when there is wallpaper in the transition.
+ verify(mDisplayContent.mAppTransition, never())
+ .overridePendingAppTransitionRemote(adapter, false /* sync */);
+ }
+
+ @Test
+ public void testTransitionGoodToGoForTaskFragments() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment changeTaskFragment =
+ createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ changeTaskFragment.getTopMostActivity().allDrawn = true;
+ spyOn(mDisplayContent.mAppTransition);
+ spyOn(emptyTaskFragment);
+
+ prepareAndTriggerAppTransition(
+ null /* openingActivity */, null /* closingActivity*/, changeTaskFragment);
+
+ // Transition not ready because there is an empty non-finishing TaskFragment.
+ verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
+
+ doReturn(true).when(emptyTaskFragment).hasChild();
+ emptyTaskFragment.remove(false /* withTransition */, "test");
+
+ mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+
+ // Transition ready because the empty (no running activity) TaskFragment is requested to be
+ // removed.
+ verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
+ }
+
+ @Test
+ public void testTransitionGoodToGoForTaskFragments_detachedApp() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment changeTaskFragment =
+ createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final TaskFragment emptyTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ changeTaskFragment.getTopMostActivity().allDrawn = true;
+ // To make sure that having a detached activity won't cause any issue.
+ final ActivityRecord detachedActivity = createActivityRecord(task);
+ detachedActivity.removeImmediately();
+ assertNull(detachedActivity.getRootTask());
+ spyOn(mDisplayContent.mAppTransition);
+ spyOn(emptyTaskFragment);
+
+ prepareAndTriggerAppTransition(
+ null /* openingActivity */, detachedActivity, changeTaskFragment);
+
+ // Transition not ready because there is an empty non-finishing TaskFragment.
+ verify(mDisplayContent.mAppTransition, never()).goodToGo(anyInt(), any());
+
+ doReturn(true).when(emptyTaskFragment).hasChild();
+ emptyTaskFragment.remove(false /* withTransition */, "test");
+
+ mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+
+ // Transition ready because the empty (no running activity) TaskFragment is requested to be
+ // removed.
+ verify(mDisplayContent.mAppTransition).goodToGo(anyInt(), any());
+ }
+
+ /** Registers remote animation for the organizer. */
+ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
+ RemoteAnimationAdapter adapter) {
+ final ITaskFragmentOrganizer iOrganizer =
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+ final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
+ mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+ }
+
+ private void prepareAndTriggerAppTransition(@Nullable ActivityRecord openingActivity,
+ @Nullable ActivityRecord closingActivity, @Nullable TaskFragment changingTaskFragment) {
+ if (openingActivity != null) {
+ mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ mDisplayContent.mOpeningApps.add(openingActivity);
+ }
+ if (closingActivity != null) {
+ mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CLOSE, 0);
+ mDisplayContent.mClosingApps.add(closingActivity);
+ }
+ if (changingTaskFragment != null) {
+ mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
+ mDisplayContent.mChangingContainers.add(changingTaskFragment);
+ }
+ mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+ }
} \ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index c3279bf05737..2ea7fdaf6348 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -18,11 +18,19 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -30,22 +38,32 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
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.Mockito.mock;
+import android.graphics.Rect;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import android.view.Display;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -77,13 +95,30 @@ public class AppTransitionTests extends WindowTestsBase {
final ActivityRecord activity = createActivityRecord(dc);
mDc.prepareAppTransition(TRANSIT_OPEN);
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
mDc.mOpeningApps.add(activity);
assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
AppTransitionController.getTransitCompatType(mDc.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null /* wallpaperTarget */, null /* oldWallpaper */,
- false /*skipAppTransitionAnimation*/));
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+ }
+
+ @Test
+ public void testKeyguardUnoccludeOcclude() {
+ final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+ final ActivityRecord activity = createActivityRecord(dc);
+
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
+ mDc.mOpeningApps.add(activity);
+ assertEquals(TRANSIT_NONE,
+ AppTransitionController.getTransitCompatType(mDc.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+
}
@Test
@@ -97,8 +132,8 @@ public class AppTransitionTests extends WindowTestsBase {
assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
AppTransitionController.getTransitCompatType(mDc.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null /* wallpaperTarget */, null /* oldWallpaper */,
- false /*skipAppTransitionAnimation*/));
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
}
@Test
@@ -112,8 +147,8 @@ public class AppTransitionTests extends WindowTestsBase {
assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
AppTransitionController.getTransitCompatType(mDc.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null /* wallpaperTarget */, null /* oldWallpaper */,
- false /*skipAppTransitionAnimation*/));
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
}
@Test
@@ -127,8 +162,8 @@ public class AppTransitionTests extends WindowTestsBase {
assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
AppTransitionController.getTransitCompatType(mDc.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null /* wallpaperTarget */, null /* oldWallpaper */,
- false /*skipAppTransitionAnimation*/));
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
}
@Test
@@ -142,8 +177,129 @@ public class AppTransitionTests extends WindowTestsBase {
assertEquals(TRANSIT_OLD_UNSET,
AppTransitionController.getTransitCompatType(mDc.mAppTransition,
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
- null /* wallpaperTarget */, null /* oldWallpaper */,
- true /*skipAppTransitionAnimation*/));
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, true /*skipAppTransitionAnimation*/));
+ }
+
+ @Test
+ public void testTaskChangeWindowingMode() {
+ final ActivityRecord activity = createActivityRecord(mDc);
+
+ mDc.prepareAppTransition(TRANSIT_OPEN);
+ mDc.prepareAppTransition(TRANSIT_CHANGE);
+ mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
+ mDc.mChangingContainers.add(activity.getTask());
+
+ assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
+ AppTransitionController.getTransitCompatType(mDc.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+ }
+
+ @Test
+ public void testTaskFragmentChange() {
+ final ActivityRecord activity = createActivityRecord(mDc);
+ final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(),
+ true /* createdByOrganizer */, true /* isEmbedded */);
+ activity.getTask().addChild(taskFragment, POSITION_TOP);
+ activity.reparent(taskFragment, POSITION_TOP);
+
+ mDc.prepareAppTransition(TRANSIT_OPEN);
+ mDc.prepareAppTransition(TRANSIT_CHANGE);
+ mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
+ mDc.mChangingContainers.add(taskFragment);
+
+ assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
+ AppTransitionController.getTransitCompatType(mDc.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+ }
+
+ @Test
+ public void testTaskFragmentOpeningTransition() {
+ final ActivityRecord activity = createHierarchyForTaskFragmentTest(
+ false /* createEmbeddedTask */);
+ activity.setVisible(false);
+
+ mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
+ mDisplayContent.mOpeningApps.add(activity);
+ assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
+ AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
+ }
+
+ @Test
+ public void testEmbeddedTaskOpeningTransition() {
+ final ActivityRecord activity = createHierarchyForTaskFragmentTest(
+ true /* createEmbeddedTask */);
+ activity.setVisible(false);
+
+ mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
+ mDisplayContent.mOpeningApps.add(activity);
+ assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
+ AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
+ }
+
+ @Test
+ public void testTaskFragmentClosingTransition() {
+ final ActivityRecord activity = createHierarchyForTaskFragmentTest(
+ false /* createEmbeddedTask */);
+ activity.setVisible(true);
+
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+ mDisplayContent.mClosingApps.add(activity);
+ assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
+ AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
+ }
+
+ @Test
+ public void testEmbeddedTaskClosingTransition() {
+ final ActivityRecord activity = createHierarchyForTaskFragmentTest(
+ true /* createEmbeddedTask */);
+ activity.setVisible(true);
+
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+ mDisplayContent.mClosingApps.add(activity);
+ assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
+ AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
+ }
+
+ /**
+ * Creates a {@link Task} with two {@link TaskFragment TaskFragments}.
+ * The bottom TaskFragment is to prevent
+ * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation
+ * target} to promote to Task or above.
+ *
+ * @param createEmbeddedTask {@code true} to create embedded Task for verified TaskFragment
+ * @return The Activity to be put in either opening or closing Activity
+ */
+ private ActivityRecord createHierarchyForTaskFragmentTest(boolean createEmbeddedTask) {
+ final Task parentTask = createTask(mDisplayContent);
+ final TaskFragment bottomTaskFragment = createTaskFragmentWithParentTask(parentTask,
+ false /* createEmbeddedTask */);
+ final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity();
+ bottomActivity.setOccludesParent(true);
+ bottomActivity.setVisible(true);
+
+ final TaskFragment verifiedTaskFragment = createTaskFragmentWithParentTask(parentTask,
+ createEmbeddedTask);
+ final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity();
+ activity.setOccludesParent(true);
+
+ return activity;
}
@Test
@@ -262,6 +418,43 @@ public class AppTransitionTests extends WindowTestsBase {
mDc.mAppTransition.getAnimationStyleResId(attrs));
}
+ @Test
+ public void testActivityRecordReparentToTaskFragment() {
+ final ActivityRecord activity = createActivityRecord(mDc);
+ final SurfaceControl activityLeash = mock(SurfaceControl.class);
+ activity.setVisibility(true);
+ activity.setSurfaceControl(activityLeash);
+ final Task task = activity.getTask();
+
+ // Add a TaskFragment of half of the Task size.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer iOrganizer =
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ final Rect taskBounds = new Rect();
+ task.getBounds(taskBounds);
+ taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
+ spyOn(taskFragment);
+ mockSurfaceFreezerSnapshot(taskFragment.mSurfaceFreezer);
+
+ assertTrue(mDc.mChangingContainers.isEmpty());
+ assertFalse(mDc.mAppTransition.isTransitionSet());
+
+ // Schedule app transition when reparent activity to a TaskFragment of different size.
+ final Rect startBounds = new Rect(activity.getBounds());
+ activity.reparent(taskFragment, POSITION_TOP);
+
+ // It should transit at TaskFragment level with snapshot on the activity surface.
+ verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
+ assertTrue(mDc.mChangingContainers.contains(taskFragment));
+ assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
+ assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
+ }
+
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
boolean mCancelled = false;
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index fd562c3f90b2..ebefeaff7f26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -75,13 +75,19 @@ public class AppWindowTokenAnimationTests extends WindowTestsBase {
@Test
public void clipAfterAnim_boundsLayerZBoosted() {
+ final Task task = mActivity.getTask();
+ final ActivityRecord topActivity = createActivityRecord(task);
+ task.assignChildLayers(mTransaction);
+
+ assertThat(topActivity.getLastLayer()).isGreaterThan(mActivity.getLastLayer());
+
mActivity.mNeedsAnimationBoundsLayer = true;
mActivity.mNeedsZBoost = true;
-
mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
ANIMATION_TYPE_APP_TRANSITION);
+
verify(mTransaction).setLayer(eq(mActivity.mAnimationBoundsLayer),
- intThat(layer -> layer >= ActivityRecord.Z_BOOST_BASE));
+ intThat(layer -> layer > topActivity.getLastLayer()));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 31d46125fd70..af21e02ce27c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -35,10 +35,10 @@ import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_LAST;
import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION;
+import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature;
-import static com.android.server.wm.DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 4e4e0edc694a..1f123ccef164 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -63,6 +63,7 @@ import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
import android.window.IDisplayAreaOrganizer;
import com.google.android.collect.Lists;
@@ -570,6 +571,31 @@ public class DisplayAreaTest extends WindowTestsBase {
}
@Test
+ public void testGetDisplayAreaInfo() {
+ final DisplayArea<WindowContainer> displayArea = new DisplayArea<>(
+ mWm, BELOW_TASKS, "NewArea", FEATURE_VENDOR_FIRST);
+ mDisplayContent.addChild(displayArea, 0);
+ final DisplayAreaInfo info = displayArea.getDisplayAreaInfo();
+
+ assertThat(info.token).isEqualTo(displayArea.mRemoteToken.toWindowContainerToken());
+ assertThat(info.configuration).isEqualTo(displayArea.getConfiguration());
+ assertThat(info.displayId).isEqualTo(mDisplayContent.getDisplayId());
+ assertThat(info.featureId).isEqualTo(displayArea.mFeatureId);
+ assertThat(info.rootDisplayAreaId).isEqualTo(mDisplayContent.mFeatureId);
+
+ final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
+ final int tdaIndex = tda.getParent().mChildren.indexOf(tda);
+ final RootDisplayArea root =
+ new DisplayAreaGroup(mWm, "TestRoot", FEATURE_VENDOR_FIRST + 1);
+ mDisplayContent.addChild(root, tdaIndex + 1);
+ displayArea.reparent(root, 0);
+
+ final DisplayAreaInfo info2 = displayArea.getDisplayAreaInfo();
+
+ assertThat(info2.rootDisplayAreaId).isEqualTo(root.mFeatureId);
+ }
+
+ @Test
public void testRegisterSameFeatureOrganizer_expectThrowsException() {
final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class);
final IBinder binder = mock(IBinder.class);
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 51e289f4d833..f3c1ec5b200e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
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.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -67,6 +68,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
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.reset;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.same;
@@ -107,9 +109,13 @@ import android.app.WindowConfiguration;
import android.app.servertransaction.FixedRotationAdjustmentsItem;
import android.content.res.Configuration;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
+import android.hardware.display.VirtualDisplay;
import android.metrics.LogMaker;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -122,6 +128,7 @@ import android.view.IDisplayWindowRotationController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -134,6 +141,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.server.LocalServices;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
@@ -141,6 +149,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.ArrayList;
import java.util.Arrays;
@@ -592,7 +602,9 @@ public class DisplayContentTests extends WindowTestsBase {
dc.setImeLayeringTarget(ws);
// Adjust bounds so that matchesRootDisplayAreaBounds() returns false.
- ws.mActivityRecord.getConfiguration().windowConfiguration.setBounds(new Rect(1, 1, 1, 1));
+ final Rect bounds = new Rect(dc.getBounds());
+ bounds.scale(0.5f);
+ ws.mActivityRecord.setBounds(bounds);
assertFalse("matchesRootDisplayAreaBounds() should return false",
ws.matchesDisplayAreaBounds());
@@ -870,19 +882,6 @@ public class DisplayContentTests extends WindowTestsBase {
.setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null);
}
- @UseTestDisplay
- @Test
- public void testClearLastFocusWhenReparentingFocusedWindow() {
- final DisplayContent defaultDisplay = mWm.getDefaultDisplayContentLocked();
- final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
- defaultDisplay, "window");
- defaultDisplay.mLastFocus = window;
- mDisplayContent.mCurrentFocus = window;
- mDisplayContent.reParentWindowToken(window.mToken);
-
- assertNull(defaultDisplay.mLastFocus);
- }
-
@Test
public void testGetPreferredOptionsPanelGravityFromDifferentDisplays() {
final DisplayContent portraitDisplay = createNewDisplay();
@@ -1217,10 +1216,10 @@ public class DisplayContentTests extends WindowTestsBase {
win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
win.getAttrs().insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
- final InsetsState requestedState = new InsetsState();
- requestedState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
- requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- win.updateRequestedVisibility(requestedState);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ win.setRequestedVisibilities(requestedVisibilities);
win.mActivityRecord.mTargetSdk = P;
performLayout(dc);
@@ -1311,7 +1310,7 @@ public class DisplayContentTests extends WindowTestsBase {
}
@UseTestDisplay(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
- W_NOTIFICATION_SHADE })
+ W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testApplyTopFixedRotationTransform() {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
@@ -1415,6 +1414,14 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals("The process should receive rotated configuration for compatibility",
expectedProcConfig, app2.app.getConfiguration());
+ // If the rotated activity requests to show IME, the IME window should use the
+ // transformation from activity to lay out in the same orientation.
+ mDisplayContent.setImeLayeringTarget(mAppWindow);
+ LocalServices.getService(WindowManagerInternal.class).onToggleImeRequested(true /* show */,
+ app.token, app.token, mDisplayContent.mDisplayId);
+ assertTrue(mImeWindow.mToken.hasFixedRotationTransform());
+ assertTrue(mImeWindow.isAnimating(PARENTS, ANIMATION_TYPE_FIXED_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);
@@ -1530,7 +1537,7 @@ public class DisplayContentTests extends WindowTestsBase {
unblockDisplayRotation(mDisplayContent);
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
app.setVisible(false);
- app.setState(Task.ActivityState.RESUMED, "test");
+ app.setState(ActivityRecord.State.RESUMED, "test");
mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
mDisplayContent.mOpeningApps.add(app);
final int newOrientation = getRotatedOrientation(mDisplayContent);
@@ -1555,6 +1562,7 @@ public class DisplayContentTests extends WindowTestsBase {
final ActivityRecord activity = createActivityRecord(mDisplayContent);
final ActivityRecord recentsActivity = createActivityRecord(mDisplayContent);
recentsActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+ doReturn(mock(RecentsAnimationController.class)).when(mWm).getRecentsAnimationController();
// Do not rotate if the recents animation is animating on top.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
@@ -1666,11 +1674,7 @@ public class DisplayContentTests extends WindowTestsBase {
public void testShellTransitRotation() {
DisplayContent dc = createNewDisplay();
- // Set-up mock shell transitions
- final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
- mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
- mAtm.getTransitionController().registerTransitionPlayer(testPlayer);
-
+ final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
final DisplayRotation dr = dc.getDisplayRotation();
doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
// Rotate 180 degree so the display doesn't have configuration change. This condition is
@@ -2251,6 +2255,183 @@ public class DisplayContentTests extends WindowTestsBase {
assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
}
+ @Test
+ public void testVirtualDisplayContent() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ surfaceControlMirrors(surfaceSize);
+
+ // WHEN creating the DisplayContent for a new virtual display.
+ final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
+ mDisplayInfo).build();
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror);
+
+ mockSession.finishMocking();
+ }
+
+ @Test
+ public void testVirtualDisplayContent_capturedAreaResized() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ SurfaceControl mirroredSurface = surfaceControlMirrors(surfaceSize);
+
+ // WHEN creating the DisplayContent for a new virtual display.
+ final DisplayContent virtualDisplay = new TestDisplayContent.Builder(mAtm,
+ mDisplayInfo).build();
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ assertThat(virtualDisplay.mTokenToMirror).isEqualTo(tokenToMirror);
+
+ float xScale = 0.7f;
+ float yScale = 2f;
+ Rect displayAreaBounds = new Rect(0, 0, Math.round(surfaceSize.x * xScale),
+ Math.round(surfaceSize.y * yScale));
+ virtualDisplay.updateMirroredSurface(mTransaction, displayAreaBounds, surfaceSize);
+
+ // THEN content in the captured DisplayArea is scaled to fit the surface size.
+ verify(mTransaction, atLeastOnce()).setMatrix(mirroredSurface, 1.0f / yScale, 0, 0,
+ 1.0f / yScale);
+ // THEN captured content is positioned in the centre of the output surface.
+ float scaledWidth = displayAreaBounds.width() / xScale;
+ float xInset = (surfaceSize.x - scaledWidth) / 2;
+ verify(mTransaction, atLeastOnce()).setPosition(mirroredSurface, xInset, 0);
+
+ mockSession.finishMocking();
+ }
+
+ @Test
+ public void testVirtualDisplayContent_withoutSurface() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl does not mirror a null surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+
+ // GIVEN a new VirtualDisplay with an associated surface.
+ final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */);
+ final int displayId = display.getDisplay().getDisplayId();
+ mWm.mRoot.onDisplayAdded(displayId);
+
+ // WHEN getting the DisplayContent for the new virtual display.
+ DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
+
+ // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
+ assertThat(actualDC.mTokenToMirror).isNull();
+
+ display.release();
+ mockSession.finishMocking();
+ }
+
+ @Test
+ public void testVirtualDisplayContent_withSurface() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(SurfaceControl.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ final IBinder tokenToMirror = setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ Point surfaceSize = new Point(
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ surfaceControlMirrors(surfaceSize);
+
+ // GIVEN a new VirtualDisplay with an associated surface.
+ final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
+ final int displayId = display.getDisplay().getDisplayId();
+ mWm.mRoot.onDisplayAdded(displayId);
+
+ // WHEN getting the DisplayContent for the new virtual display.
+ DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ assertThat(actualDC.mTokenToMirror).isEqualTo(tokenToMirror);
+
+ display.release();
+ mockSession.finishMocking();
+ }
+
+ private class TestToken extends Binder {
+ }
+
+ /**
+ * Creates a WindowToken associated with the default task DisplayArea, in order for that
+ * DisplayArea to be mirrored.
+ */
+ private IBinder setUpDefaultTaskDisplayAreaWindowToken() {
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ final IBinder tokenToMirror = new TestToken();
+ doReturn(tokenToMirror).when(mWm.mDisplayManagerInternal).getWindowTokenClientToMirror(
+ anyInt());
+
+ // GIVEN the default task display area is represented by the WindowToken.
+ spyOn(mWm.mWindowContextListenerController);
+ doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
+ mWm.mWindowContextListenerController).getContainer(any());
+ return tokenToMirror;
+ }
+
+ /**
+ * SurfaceControl successfully creates a mirrored surface of the given size.
+ */
+ private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
+ // Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
+ SurfaceControl mirroredSurface = new SurfaceControl.Builder()
+ .setName("mirroredSurface")
+ .setBufferSize(surfaceSize.x, surfaceSize.y)
+ .setCallsite("mirrorSurface")
+ .build();
+ doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
+ doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
+ anyInt());
+ return mirroredSurface;
+ }
+
+ private VirtualDisplay createVirtualDisplay(Point size, Surface surface) {
+ return mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", size.x, size.y,
+ DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
+ }
+
private void removeRootTaskTests(Runnable runnable) {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
@@ -2261,10 +2442,10 @@ public class DisplayContentTests extends WindowTestsBase {
ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task rootTask4 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, ON_TOP);
- final Task task1 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask1).build();
- final Task task2 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask2).build();
- final Task task3 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask3).build();
- final Task task4 = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(rootTask4).build();
+ final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask1).build();
+ final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask2).build();
+ final Task task3 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask3).build();
+ final Task task4 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask4).build();
// Reordering root tasks while removing root tasks.
doAnswer(invocation -> {
@@ -2329,7 +2510,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop)));
}
- private static int getRotatedOrientation(DisplayContent dc) {
+ static int getRotatedOrientation(DisplayContent dc) {
return dc.mBaseDisplayWidth > dc.mBaseDisplayHeight
? SCREEN_ORIENTATION_PORTRAIT
: SCREEN_ORIENTATION_LANDSCAPE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
index 004e45a8be4b..b2b844b94d43 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyInsetsTests.java
@@ -180,23 +180,23 @@ public class DisplayPolicyInsetsTests extends DisplayPolicyTestsBase {
}
private int getNonDecorDisplayWidth(DisplayInfo di) {
- return mDisplayPolicy.getNonDecorDisplayWidth(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ return mDisplayPolicy.getNonDecorDisplaySize(di.logicalWidth, di.logicalHeight,
+ di.rotation, 0 /* ui */, di.displayCutout).x;
}
private int getNonDecorDisplayHeight(DisplayInfo di) {
- return mDisplayPolicy.getNonDecorDisplayHeight(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ return mDisplayPolicy.getNonDecorDisplaySize(di.logicalWidth, di.logicalHeight,
+ di.rotation, 0 /* ui */, di.displayCutout).y;
}
private int getConfigDisplayWidth(DisplayInfo di) {
- return mDisplayPolicy.getConfigDisplayWidth(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
+ di.rotation, 0 /* ui */, di.displayCutout).x;
}
private int getConfigDisplayHeight(DisplayInfo di) {
- return mDisplayPolicy.getConfigDisplayHeight(di.logicalWidth, di.logicalHeight,
- di.rotation, 0 /* ui */, di.displayCutout);
+ return mDisplayPolicy.getConfigDisplaySize(di.logicalWidth, di.logicalHeight,
+ di.rotation, 0 /* ui */, di.displayCutout).y;
}
private static DisplayInfo displayInfoForRotation(int rotation, boolean withDisplayCutout) {
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 03304bb9456a..4957ab96ace1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -24,7 +24,7 @@ import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -50,13 +50,13 @@ import static org.hamcrest.Matchers.is;
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.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.testng.Assert.expectThrows;
import android.graphics.Insets;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
@@ -65,11 +65,11 @@ import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
-import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -109,18 +109,13 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
mWindow = spy(createWindow(null, TYPE_APPLICATION, "window"));
// We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from
// changing those frames.
- doNothing().when(mWindow).computeFrame();
-
- final WindowManager.LayoutParams attrs = mWindow.mAttrs;
- attrs.width = MATCH_PARENT;
- attrs.height = MATCH_PARENT;
- attrs.format = PixelFormat.TRANSLUCENT;
+ doNothing().when(mWindow).computeFrame(any());
spyOn(mStatusBarWindow);
spyOn(mNavBarWindow);
// Disabling this call for most tests since it can override the systemUiFlags when called.
- doReturn(false).when(mDisplayPolicy).updateSystemUiVisibilityLw();
+ doNothing().when(mDisplayPolicy).updateSystemBarAttributes();
updateDisplayFrames();
}
@@ -219,7 +214,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
}
@Test
- public void addingWindow_ignoresInsetsTypes_InWindowTypeWithPredefinedInsets() {
+ public void addingWindow_InWindowTypeWithPredefinedInsets() {
mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one.
WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar");
win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
@@ -230,7 +225,13 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
InsetsSourceProvider provider =
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR);
- assertNotEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ // In the new flexible insets setup, the insets frame should always respect the window
+ // layout result.
+ assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+ } else {
+ assertNotEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+ }
}
@Test
@@ -478,9 +479,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mDisplayContent.getInsetsStateController().getRawInsetsState()
.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
- final InsetsState requestedState = new InsetsState();
- requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mWindow.updateRequestedVisibility(requestedState);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ mWindow.setRequestedVisibilities(requestedVisibilities);
addWindowWithRawInsetsState(mWindow);
mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -498,9 +499,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
mDisplayContent.getInsetsStateController().getRawInsetsState()
.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
- final InsetsState requestedState = new InsetsState();
- requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- mWindow.updateRequestedVisibility(requestedState);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ mWindow.setRequestedVisibilities(requestedVisibilities);
mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
addWindowWithRawInsetsState(mWindow);
@@ -733,10 +734,12 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
@Test
public void testFixedRotationInsetsSourceFrame() {
+ mDisplayContent.mBaseDisplayHeight = DISPLAY_HEIGHT;
+ mDisplayContent.mBaseDisplayWidth = DISPLAY_WIDTH;
doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent)
.rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
- mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController()
- .getRawInsetsState().peekSource(ITYPE_STATUS_BAR));
+ mWindow.mAboveInsetsState.set(
+ mDisplayContent.getInsetsStateController().getRawInsetsState());
final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow)
.getSource(ITYPE_STATUS_BAR).getFrame();
mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
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 b793be74c033..69700052ce74 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
@@ -37,7 +38,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT;
import static org.junit.Assert.assertEquals;
@@ -107,8 +107,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
@Test
public void testChooseNavigationColorWindowLw() {
- final WindowState opaque = createOpaqueFullscreen(false);
-
+ final WindowState candidate = createOpaqueFullscreen(false);
final WindowState dimmingImTarget = createDimmingDialogWindow(true);
final WindowState dimmingNonImTarget = createDimmingDialogWindow(false);
@@ -116,45 +115,51 @@ public class DisplayPolicyTests extends WindowTestsBase {
final WindowState invisibleIme = createInputMethodWindow(false, true, false);
final WindowState imeNonDrawNavBar = createInputMethodWindow(true, false, false);
- // If everything is null, return null
+ // If everything is null, return null.
assertNull(null, DisplayPolicy.chooseNavigationColorWindowLw(
- null, null, null, NAV_BAR_BOTTOM));
+ null, null, NAV_BAR_BOTTOM));
- assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, opaque, null, NAV_BAR_BOTTOM));
+ // If no IME windows, return candidate window.
+ assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
+ candidate, null, NAV_BAR_BOTTOM));
assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, dimmingImTarget, null, NAV_BAR_BOTTOM));
+ dimmingImTarget, null, NAV_BAR_BOTTOM));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, dimmingNonImTarget, null, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, null, NAV_BAR_BOTTOM));
- assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- null, null, visibleIme, NAV_BAR_BOTTOM));
- assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- null, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
+ // If IME is not visible, return candidate window.
+ assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw(
+ null, invisibleIme, NAV_BAR_BOTTOM));
+ assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
+ candidate, invisibleIme, NAV_BAR_BOTTOM));
+ assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
+ dimmingImTarget, invisibleIme, NAV_BAR_BOTTOM));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- null, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, invisibleIme, NAV_BAR_BOTTOM));
+
+ // If IME is visible, return candidate when the candidate window is not dimming.
assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, opaque, visibleIme, NAV_BAR_BOTTOM));
+ null, visibleIme, NAV_BAR_BOTTOM));
assertEquals(visibleIme, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, dimmingImTarget, visibleIme, NAV_BAR_BOTTOM));
- assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
+ candidate, visibleIme, NAV_BAR_BOTTOM));
- assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, opaque, invisibleIme, NAV_BAR_BOTTOM));
- assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, opaque, invisibleIme, NAV_BAR_BOTTOM));
- assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, opaque, visibleIme, NAV_BAR_RIGHT));
+ // 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));
+ assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
+ dimmingNonImTarget, visibleIme, NAV_BAR_BOTTOM));
// Only IME windows that have FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS should be navigation color
// window.
- assertEquals(opaque, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, opaque, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ assertEquals(null, DisplayPolicy.chooseNavigationColorWindowLw(
+ null, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ assertEquals(candidate, DisplayPolicy.chooseNavigationColorWindowLw(
+ candidate, imeNonDrawNavBar, NAV_BAR_BOTTOM));
assertEquals(dimmingImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ dimmingImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
assertEquals(dimmingNonImTarget, DisplayPolicy.chooseNavigationColorWindowLw(
- opaque, dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
+ dimmingNonImTarget, imeNonDrawNavBar, NAV_BAR_BOTTOM));
}
@UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
@@ -182,59 +187,27 @@ public class DisplayPolicyTests extends WindowTestsBase {
// If there is no window, APPEARANCE_LIGHT_NAVIGATION_BARS is not allowed.
assertEquals(0,
- displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, null, null,
- null, null));
-
- // Opaque top fullscreen window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- 0, opaqueDarkNavBar, opaqueDarkNavBar, null, opaqueDarkNavBar));
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, opaqueDarkNavBar, null,
- opaqueDarkNavBar));
- assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
- displayPolicy.updateLightNavigationBarLw(0, opaqueLightNavBar,
- opaqueLightNavBar, null, opaqueLightNavBar));
- assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
- displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS,
- opaqueLightNavBar, opaqueLightNavBar, null, opaqueLightNavBar));
-
- // Dimming window clears APPEARANCE_LIGHT_NAVIGATION_BARS.
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- 0, opaqueDarkNavBar, dimming, null, dimming));
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- 0, opaqueLightNavBar, dimming, null, dimming));
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar, dimming, null, dimming));
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, null, dimming));
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, dimming, imeDrawLightNavBar,
- dimming));
-
- // IME window clears APPEARANCE_LIGHT_NAVIGATION_BARS
- assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, null, null, imeDrawDarkNavBar,
- imeDrawDarkNavBar));
+ displayPolicy.updateLightNavigationBarLw(APPEARANCE_LIGHT_NAVIGATION_BARS, null));
- // Even if the top fullscreen has APPEARANCE_LIGHT_NAVIGATION_BARS, IME window wins.
+ // Control window overrides APPEARANCE_LIGHT_NAVIGATION_BARS flag.
+ assertEquals(0, displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar));
assertEquals(0, displayPolicy.updateLightNavigationBarLw(
- APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar, opaqueLightNavBar,
- imeDrawDarkNavBar, imeDrawDarkNavBar));
-
- // IME window should be able to use APPEARANCE_LIGHT_NAVIGATION_BARS.
- assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS,
- displayPolicy.updateLightNavigationBarLw(0, opaqueDarkNavBar,
- opaqueDarkNavBar, imeDrawLightNavBar, imeDrawLightNavBar));
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueDarkNavBar));
+ assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, displayPolicy.updateLightNavigationBarLw(
+ 0, opaqueLightNavBar));
+ assertEquals(APPEARANCE_LIGHT_NAVIGATION_BARS, displayPolicy.updateLightNavigationBarLw(
+ APPEARANCE_LIGHT_NAVIGATION_BARS, opaqueLightNavBar));
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
+ @UseTestDisplay(addWindows = {W_ACTIVITY, W_STATUS_BAR})
@Test
public void testComputeTopFullscreenOpaqueWindow() {
final WindowManager.LayoutParams attrs = mAppWindow.mAttrs;
attrs.x = attrs.y = 0;
attrs.height = attrs.width = WindowManager.LayoutParams.MATCH_PARENT;
final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ policy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
+
policy.applyPostLayoutPolicyLw(
mAppWindow, attrs, null /* attached */, null /* imeTarget */);
@@ -274,28 +247,38 @@ public class DisplayPolicyTests extends WindowTestsBase {
@Test
public void testOverlappingWithNavBar() {
+ final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+ navSource.setFrame(new Rect(100, 200, 200, 300));
+ testOverlappingWithNavBarType(navSource);
+ }
+
+ @Test
+ public void testOverlappingWithExtraNavBar() {
+ final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ navSource.setFrame(new Rect(100, 200, 200, 300));
+ testOverlappingWithNavBarType(navSource);
+ }
+
+ private void testOverlappingWithNavBarType(InsetsSource navSource) {
final WindowState targetWin = createApplicationWindow();
final WindowFrames winFrame = targetWin.getWindowFrames();
winFrame.mFrame.set(new Rect(100, 100, 200, 200));
-
- final WindowState navigationBar = createNavigationBarWindow();
-
- navigationBar.getFrame().set(new Rect(100, 200, 200, 300));
+ targetWin.mAboveInsetsState.addSource(navSource);
assertFalse("Freeform is overlapping with navigation bar",
- DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar));
+ DisplayPolicy.isOverlappingWithNavBar(targetWin));
winFrame.mFrame.set(new Rect(100, 101, 200, 201));
assertTrue("Freeform should be overlapping with navigation bar (bottom)",
- DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar));
+ DisplayPolicy.isOverlappingWithNavBar(targetWin));
winFrame.mFrame.set(new Rect(99, 200, 199, 300));
assertTrue("Freeform should be overlapping with navigation bar (right)",
- DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar));
+ DisplayPolicy.isOverlappingWithNavBar(targetWin));
winFrame.mFrame.set(new Rect(199, 200, 299, 300));
assertTrue("Freeform should be overlapping with navigation bar (left)",
- DisplayPolicy.isOverlappingWithNavBar(targetWin, navigationBar));
+ DisplayPolicy.isOverlappingWithNavBar(targetWin));
}
private WindowState createNavigationBarWindow() {
@@ -313,7 +296,9 @@ public class DisplayPolicyTests extends WindowTestsBase {
displayInfo.logicalHeight = 2000;
displayInfo.rotation = ROTATION_0;
- displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
+ 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);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 683ed889d283..1d2baab934e5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -31,6 +31,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
import android.content.Context;
import android.content.ContextWrapper;
@@ -58,8 +59,6 @@ public class DisplayPolicyTestsBase extends WindowTestsBase {
static final int DISPLAY_HEIGHT = 1000;
static final int DISPLAY_DENSITY = 320;
- static final int STATUS_BAR_HEIGHT = 10;
- static final int NAV_BAR_HEIGHT = 15;
static final int DISPLAY_CUTOUT_HEIGHT = 8;
static final int IME_HEIGHT = 415;
@@ -77,13 +76,12 @@ public class DisplayPolicyTestsBase extends WindowTestsBase {
final TestContextWrapper context = new TestContextWrapper(
mDisplayPolicy.getContext(), mDisplayPolicy.getCurrentUserResources());
final TestableResources resources = context.getResourceMocker();
- resources.addOverride(R.dimen.status_bar_height_portrait, STATUS_BAR_HEIGHT);
- resources.addOverride(R.dimen.status_bar_height_landscape, STATUS_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_height, NAV_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_width, NAV_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_frame_height_landscape, NAV_BAR_HEIGHT);
resources.addOverride(R.dimen.navigation_bar_frame_height, NAV_BAR_HEIGHT);
+ doReturn(STATUS_BAR_HEIGHT).when(mDisplayPolicy).getStatusBarHeightForRotation(anyInt());
doReturn(resources.getResources()).when(mDisplayPolicy).getCurrentUserResources();
doReturn(true).when(mDisplayPolicy).hasNavigationBar();
doReturn(true).when(mDisplayPolicy).hasStatusBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 223dc3121cd6..32cca47b991c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -37,6 +37,7 @@ 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.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -44,6 +45,7 @@ import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
+import android.content.pm.ShortcutServiceInternal;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.IBinder;
@@ -58,6 +60,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -70,6 +73,8 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
@@ -87,6 +92,7 @@ import java.util.concurrent.TimeUnit;
public class DragDropControllerTests extends WindowTestsBase {
private static final int TIMEOUT_MS = 3000;
private static final int TEST_UID = 12345;
+ private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE;
private static final int TEST_PID = 67890;
private static final String TEST_PACKAGE = "com.test.package";
@@ -97,14 +103,20 @@ public class DragDropControllerTests extends WindowTestsBase {
static class TestDragDropController extends DragDropController {
private Runnable mCloseCallback;
boolean mDeferDragStateClosed;
+ boolean mIsAccessibilityDrag;
TestDragDropController(WindowManagerService service, Looper looper) {
super(service, looper);
}
void setOnClosedCallbackLocked(Runnable runnable) {
- assertTrue(dragDropActiveLocked());
- mCloseCallback = runnable;
+ if (mIsAccessibilityDrag) {
+ // Accessibility does not use animation
+ assertTrue(!dragDropActiveLocked());
+ } else {
+ assertTrue(dragDropActiveLocked());
+ mCloseCallback = runnable;
+ }
}
@Override
@@ -171,6 +183,10 @@ public class DragDropControllerTests extends WindowTestsBase {
}
latch = new CountDownLatch(1);
mTarget.setOnClosedCallbackLocked(latch::countDown);
+ if (mTarget.mIsAccessibilityDrag) {
+ mTarget.mIsAccessibilityDrag = false;
+ return;
+ }
assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)));
}
@@ -180,6 +196,12 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
+ public void testA11yDragFlow() {
+ mTarget.mIsAccessibilityDrag = true;
+ doA11yDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
+ }
+
+ @Test
public void testPerformDrag_NullDataWithGrantUri() {
doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
}
@@ -369,6 +391,32 @@ public class DragDropControllerTests extends WindowTestsBase {
}
}
+ @Test
+ public void testValidateProfileAppShortcutArguments_notCallingUid() {
+ doReturn(PERMISSION_GRANTED).when(mWm.mContext)
+ .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS));
+ final Session session = Mockito.spy(new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float scale) {}
+ }));
+ final ShortcutServiceInternal shortcutService = mock(ShortcutServiceInternal.class);
+ final Intent[] shortcutIntents = new Intent[1];
+ shortcutIntents[0] = new Intent();
+ doReturn(shortcutIntents).when(shortcutService).createShortcutIntents(anyInt(), any(),
+ any(), any(), anyInt(), anyInt(), anyInt());
+ LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+ LocalServices.addService(ShortcutServiceInternal.class, shortcutService);
+
+ ArgumentCaptor<Integer> callingUser = ArgumentCaptor.forClass(Integer.class);
+ session.validateAndResolveDragMimeTypeExtras(
+ createClipDataForShortcut("test_package", "test_shortcut_id",
+ mock(UserHandle.class)),
+ TEST_PROFILE_UID, TEST_PID, TEST_PACKAGE);
+ verify(shortcutService).createShortcutIntents(callingUser.capture(), any(),
+ any(), any(), anyInt(), anyInt(), anyInt());
+ assertTrue(callingUser.getValue() == UserHandle.getUserId(TEST_PROFILE_UID));
+ }
+
private ClipData createClipDataForShortcut(String packageName, String shortcutId,
UserHandle user) {
final Intent data = new Intent();
@@ -436,4 +484,22 @@ public class DragDropControllerTests extends WindowTestsBase {
appSession.kill();
}
}
+
+ private void doA11yDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
+ spyOn(mTarget);
+ AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class);
+ when(accessibilityManager.isEnabled()).thenReturn(true);
+ doReturn(accessibilityManager).when(mTarget).getAccessibilityManager();
+ startA11yDrag(flags, data, () -> {
+ boolean dropped = mTarget.dropForAccessibility(mWindow.mClient, dropX, dropY);
+ mToken = mWindow.mClient.asBinder();
+ });
+ }
+
+ private void startA11yDrag(int flags, ClipData data, Runnable r) {
+ mToken = mTarget.performDrag(0, 0, mWindow.mClient,
+ flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data);
+ assertNotNull(mToken);
+ r.run();
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index d7daa57cc9da..407f9cfdbe3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_ON;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -70,7 +71,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase {
@Before
public void setUp() throws Exception {
- // Let the Display to be created with the DualDisplay policy.
+ // Let the Display be created with the DualDisplay policy.
final DisplayAreaPolicy.Provider policyProvider =
new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
@@ -78,6 +79,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase {
mController = new InputMethodMenuController(mock(InputMethodManagerService.class));
mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent
.Builder(mAtm, 1000, 1000).build();
+ mSecondaryDisplay.getDisplayInfo().state = STATE_ON;
// Mock addWindowTokenWithOptions to create a test window token.
mIWindowManager = WindowManagerGlobal.getWindowManagerService();
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 bf3ed692dc8e..ea5bf52af905 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -48,6 +49,7 @@ import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -80,7 +82,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
@Test
- public void testControlsForDispatch_dockedStackVisible() {
+ public void testControlsForDispatch_dockedTaskVisible() {
addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
@@ -93,25 +95,26 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
@Test
- public void testControlsForDispatch_freeformStackVisible() {
+ public void testControlsForDispatch_multiWindowTaskVisible() {
addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
- final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+ final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
- // The app must not control any bars.
+ // The app must not control any system bars.
assertNull(controls);
}
@Test
- public void testControlsForDispatch_dockedDividerControllerResizing() {
+ public void testControlsForDispatch_freeformTaskVisible() {
addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
- mDisplayContent.getDockedDividerController().setResizing(true);
- final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+ final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
+ ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+ final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
// The app must not control any system bars.
assertNull(controls);
@@ -179,9 +182,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
// Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar.
final WindowState fullscreenApp = addWindow(TYPE_APPLICATION, "fullscreenApp");
- final InsetsState requestedState = new InsetsState();
- requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- fullscreenApp.updateRequestedVisibility(requestedState);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ fullscreenApp.setRequestedVisibilities(requestedVisibilities);
// Add a non-fullscreen dialog window.
final WindowState dialog = addWindow(TYPE_APPLICATION, "dialog");
@@ -214,9 +217,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
// Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
- final InsetsState newRequestedState = new InsetsState();
- newRequestedState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- newFocusedFullscreenApp.updateRequestedVisibility(newRequestedState);
+ final InsetsVisibilities newRequestedVisibilities = new InsetsVisibilities();
+ newRequestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
+ newFocusedFullscreenApp.setRequestedVisibilities(newRequestedVisibilities);
// Make sure status bar is hidden by previous insets state.
mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp);
@@ -277,10 +280,10 @@ public class InsetsPolicyTest extends WindowTestsBase {
doNothing().when(policy).startAnimation(anyBoolean(), any());
// Make both system bars invisible.
- final InsetsState requestedState = new InsetsState();
- requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
- requestedState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
- mAppWindow.updateRequestedVisibility(requestedState);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
+ mAppWindow.setRequestedVisibilities(requestedVisibilities);
policy.updateBarControlTarget(mAppWindow);
waitUntilWindowAnimatorIdle();
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
@@ -288,7 +291,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
+ policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+ true /* isGestureOnSystemBar */);
waitUntilWindowAnimatorIdle();
final InsetsSourceControl[] controls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -316,7 +320,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
- policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
+ policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+ true /* isGestureOnSystemBar */);
waitUntilWindowAnimatorIdle();
final InsetsSourceControl[] controls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -348,7 +353,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(mAppWindow);
- policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
+ policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+ true /* isGestureOnSystemBar */);
waitUntilWindowAnimatorIdle();
InsetsSourceControl[] controls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow);
@@ -371,7 +377,10 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- mAppWindow.updateRequestedVisibility(state);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
+ requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, true);
+ mAppWindow.setRequestedVisibilities(requestedVisibilities);
policy.onInsetsModified(mAppWindow);
waitUntilWindowAnimatorIdle();
@@ -396,7 +405,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
policy.updateBarControlTarget(app);
- policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR});
+ policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
+ true /* isGestureOnSystemBar */);
final InsetsSourceControl[] controls =
mDisplayContent.getInsetsStateController().getControlsForDispatch(app);
policy.updateBarControlTarget(app2);
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 c483ae9fa4c5..2987f943f1c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -31,7 +31,7 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -203,9 +203,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindow(statusBar, null, null);
mProvider.updateControlForTarget(target, false /* force */);
- InsetsState state = new InsetsState();
- state.getSource(ITYPE_STATUS_BAR).setVisible(false);
- target.updateRequestedVisibility(state);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ target.setRequestedVisibilities(requestedVisibilities);
mProvider.updateClientVisibility(target);
assertFalse(mSource.isVisible());
}
@@ -216,9 +216,9 @@ public class InsetsSourceProviderTest extends WindowTestsBase {
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindow(statusBar, null, null);
- InsetsState state = new InsetsState();
- state.getSource(ITYPE_STATUS_BAR).setVisible(false);
- target.updateRequestedVisibility(state);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ target.setRequestedVisibilities(requestedVisibilities);
mProvider.updateClientVisibility(target);
assertTrue(mSource.isVisible());
}
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 80961d7afb70..94bc7f2dc0d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -48,6 +48,7 @@ import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -174,10 +175,10 @@ public class InsetsStateControllerTest extends WindowTestsBase {
mImeWindow.setHasSurface(true);
getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
getController().onImeControlTargetChanged(mDisplayContent.getImeTarget(IME_TARGET_INPUT));
- final InsetsState requestedState = new InsetsState();
- requestedState.getSource(ITYPE_IME).setVisible(true);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_IME, true);
mDisplayContent.getImeTarget(IME_TARGET_INPUT).getWindow()
- .updateRequestedVisibility(requestedState);
+ .setRequestedVisibilities(requestedVisibilities);
getController().onInsetsModified(mDisplayContent.getImeTarget(IME_TARGET_INPUT));
// Send our spy window (app) into the system so that we can detect the invocation.
@@ -331,7 +332,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
provider.getSource().setVisible(false);
- mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR });
+ mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR },
+ true /* isGestureOnSystemBar */);
assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
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 7cb7c79d63a0..8a6db2c62a10 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -117,7 +117,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase {
Task rootTask = mTestDisplay.getDefaultTaskDisplayArea()
.createRootTask(TEST_WINDOWING_MODE, ACTIVITY_TYPE_STANDARD, /* onTop */ true);
mTestTask = new TaskBuilder(mSupervisor).setComponent(TEST_COMPONENT)
- .setParentTask(rootTask).build();
+ .setParentTaskFragment(rootTask).build();
mTestTask.mUserId = TEST_USER_ID;
mTestTask.mLastNonFullscreenBounds = TEST_BOUNDS;
mTestTask.setHasBeenVisible(true);
@@ -353,7 +353,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase {
final Task anotherTaskOfTheSameUser = new TaskBuilder(mSupervisor)
.setComponent(ALTERNATIVE_COMPONENT)
.setUserId(TEST_USER_ID)
- .setParentTask(stack)
+ .setParentTaskFragment(stack)
.build();
anotherTaskOfTheSameUser.setWindowingMode(WINDOWING_MODE_FREEFORM);
anotherTaskOfTheSameUser.setBounds(200, 300, 400, 500);
@@ -365,7 +365,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase {
final Task anotherTaskOfDifferentUser = new TaskBuilder(mSupervisor)
.setComponent(TEST_COMPONENT)
.setUserId(ALTERNATIVE_USER_ID)
- .setParentTask(stack)
+ .setParentTaskFragment(stack)
.build();
anotherTaskOfDifferentUser.setWindowingMode(WINDOWING_MODE_FREEFORM);
anotherTaskOfDifferentUser.setBounds(300, 400, 500, 600);
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 647a898a5361..1e86522a2307 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -62,7 +62,8 @@ public class LetterboxTest {
mSurfaces = new SurfaceControlMocker();
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
() -> mAreCornersRounded, () -> Color.valueOf(mColor),
- () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha);
+ () -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
+ /* doubleTapCallback= */ x -> {});
mTransaction = spy(StubTransaction.class);
}
@@ -200,28 +201,37 @@ public class LetterboxTest {
assertTrue(mLetterbox.needsApplySurfaceChanges());
mLetterbox.applySurfaceChanges(mTransaction);
- verify(mTransaction).setAlpha(mSurfaces.top, mDarkScrimAlpha);
+ verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha);
}
@Test
- public void testApplySurfaceChanges_cornersNotRounded_surfaceBehindNotCreated() {
+ public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() {
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
mLetterbox.applySurfaceChanges(mTransaction);
- assertNull(mSurfaces.behind);
+ assertNull(mSurfaces.fullWindowSurface);
}
@Test
- public void testApplySurfaceChanges_cornersRounded_surfaceBehindCreated() {
+ public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() {
mAreCornersRounded = true;
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
mLetterbox.applySurfaceChanges(mTransaction);
- assertNotNull(mSurfaces.behind);
+ assertNotNull(mSurfaces.fullWindowSurface);
}
@Test
- public void testNotIntersectsOrFullyContains_cornersRounded_doesNotCheckSurfaceBehind() {
+ public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() {
+ mHasWallpaperBackground = true;
+ mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+ mLetterbox.applySurfaceChanges(mTransaction);
+
+ assertNotNull(mSurfaces.fullWindowSurface);
+ }
+
+ @Test
+ public void testNotIntersectsOrFullyContains_cornersRounded() {
mAreCornersRounded = true;
mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
mLetterbox.applySurfaceChanges(mTransaction);
@@ -249,8 +259,8 @@ public class LetterboxTest {
public SurfaceControl right;
private SurfaceControl.Builder mBottomBuilder;
public SurfaceControl bottom;
- private SurfaceControl.Builder mBehindBuilder;
- public SurfaceControl behind;
+ private SurfaceControl.Builder mFullWindowSurfaceBuilder;
+ public SurfaceControl fullWindowSurface;
@Override
public SurfaceControl.Builder get() {
@@ -265,8 +275,8 @@ public class LetterboxTest {
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("behind")) {
- mBehindBuilder = (SurfaceControl.Builder) i.getMock();
+ } else if (((String) i.getArgument(0)).contains("fullWindow")) {
+ mFullWindowSurfaceBuilder = (SurfaceControl.Builder) i.getMock();
}
return i.getMock();
});
@@ -281,8 +291,8 @@ public class LetterboxTest {
right = control;
} else if (i.getMock() == mBottomBuilder) {
bottom = control;
- } else if (i.getMock() == mBehindBuilder) {
- behind = control;
+ } else if (i.getMock() == mFullWindowSurfaceBuilder) {
+ fullWindowSurface = control;
}
return control;
}).when(builder).build();
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 5af68021b201..284728397c9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -331,7 +331,7 @@ public class RecentTasksTest extends WindowTestsBase {
// other task
Task task1 = createTaskBuilder(".Task1")
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
- .setParentTask(mTaskContainer.getRootHomeTask()).build();
+ .setParentTaskFragment(mTaskContainer.getRootHomeTask()).build();
Task task2 = createTaskBuilder(".Task1")
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
.build();
@@ -471,8 +471,8 @@ public class RecentTasksTest extends WindowTestsBase {
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();
+ final Task child1 = createTaskBuilder(".Task1").setParentTaskFragment(root).build();
+ final Task child2 = createTaskBuilder(".Task2").setParentTaskFragment(root).build();
doReturn(true).when(child1).isOrganized();
doReturn(false).when(child2).isOrganized();
mRecentTasks.add(root);
@@ -508,7 +508,8 @@ public class RecentTasksTest extends WindowTestsBase {
// tasks because their intents are identical.
mRecentTasks.add(task1);
// Go home to trigger the removal of untracked tasks.
- mRecentTasks.add(createTaskBuilder(".Home").setParentTask(mTaskContainer.getRootHomeTask())
+ mRecentTasks.add(createTaskBuilder(".Home")
+ .setParentTaskFragment(mTaskContainer.getRootHomeTask())
.build());
triggerIdleToTrim();
@@ -675,7 +676,7 @@ public class RecentTasksTest extends WindowTestsBase {
public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() {
// Create some set of tasks, some of which are visible and some are not
Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask")
- .setParentTask(mTaskContainer.getRootHomeTask())
+ .setParentTaskFragment(mTaskContainer.getRootHomeTask())
.build();
homeTask.mUserSetupComplete = true;
mRecentTasks.add(homeTask);
@@ -696,7 +697,7 @@ public class RecentTasksTest extends WindowTestsBase {
t1.mUserSetupComplete = true;
mRecentTasks.add(t1);
Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask")
- .setParentTask(mTaskContainer.getRootHomeTask())
+ .setParentTaskFragment(mTaskContainer.getRootHomeTask())
.build();
homeTask.mUserSetupComplete = true;
mRecentTasks.add(homeTask);
@@ -788,6 +789,19 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
+ public void testVisibleEmbeddedTask_expectNotVisible() {
+ Task task = createTaskBuilder(".Task")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ doReturn(true).when(task).isEmbedded();
+ mRecentTasks.add(task);
+
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertFalse("embedded task should not be visible recents",
+ mRecentTasks.isVisibleRecentTask(task));
+ }
+
+ @Test
public void testFreezeTaskListOrder_reorderExistingTask() {
// Add some tasks
mRecentTasks.add(mTasks.get(0));
@@ -859,6 +873,40 @@ public class RecentTasksTest extends WindowTestsBase {
}
@Test
+ public void testFreezeTaskListOrder_replaceTask() {
+ // Create two tasks with the same affinity
+ Task affinityTask1 = createTaskBuilder(".AffinityTask1")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ Task affinityTask2 = createTaskBuilder(".AffinityTask2")
+ .setFlags(FLAG_ACTIVITY_NEW_TASK)
+ .build();
+ affinityTask2.affinity = affinityTask1.affinity = "affinity";
+
+ // Add some tasks
+ mRecentTasks.add(mTasks.get(0));
+ mRecentTasks.add(affinityTask1);
+ mRecentTasks.add(mTasks.get(1));
+ mCallbacksRecorder.clear();
+
+ // Freeze the list
+ mRecentTasks.setFreezeTaskListReordering();
+ assertTrue(mRecentTasks.isFreezeTaskListReorderingSet());
+
+ // Add the affinity task
+ mRecentTasks.add(affinityTask2);
+
+ assertRecentTasksOrder(mTasks.get(1),
+ affinityTask2,
+ mTasks.get(0));
+
+ assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+ assertThat(mCallbacksRecorder.mAdded).contains(affinityTask2);
+ assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+ assertThat(mCallbacksRecorder.mRemoved).contains(affinityTask1);
+ }
+
+ @Test
public void testFreezeTaskListOrder_timeout() {
// Add some tasks
mRecentTasks.add(mTasks.get(0));
@@ -902,10 +950,10 @@ public class RecentTasksTest extends WindowTestsBase {
// Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
// the tasks belong in stacks above the home stack
- mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build());
- mRecentTasks.add(createTaskBuilder(".Task1").setParentTask(aboveHomeStack).build());
- mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build());
- mRecentTasks.add(createTaskBuilder(".Task3").setParentTask(aboveHomeStack).build());
+ mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task1").setParentTaskFragment(aboveHomeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(aboveHomeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task3").setParentTaskFragment(aboveHomeStack).build());
triggerTrimAndAssertNoTasksTrimmed();
}
@@ -923,11 +971,11 @@ public class RecentTasksTest extends WindowTestsBase {
// Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
// the home stack is trimmed once a new task is added
final Task behindHomeTask = createTaskBuilder(".Task1")
- .setParentTask(behindHomeStack)
+ .setParentTaskFragment(behindHomeStack)
.build();
mRecentTasks.add(behindHomeTask);
- mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build());
- mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build());
+ mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(aboveHomeStack).build());
triggerTrimAndAssertTrimmed(behindHomeTask);
}
@@ -943,10 +991,12 @@ public class RecentTasksTest extends WindowTestsBase {
// Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
// removed
- mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeTask).build());
- mRecentTasks.add(createTaskBuilder(".Task1").setParentTask(otherDisplayRootTask).build());
- mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(otherDisplayRootTask).build());
- mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTask(homeTask).build());
+ mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTaskFragment(homeTask).build());
+ mRecentTasks.add(createTaskBuilder(".Task1").setParentTaskFragment(otherDisplayRootTask)
+ .build());
+ mRecentTasks.add(createTaskBuilder(".Task2").setParentTaskFragment(otherDisplayRootTask)
+ .build());
+ mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTaskFragment(homeTask).build());
triggerTrimAndAssertNoTasksTrimmed();
}
@@ -976,7 +1026,7 @@ public class RecentTasksTest extends WindowTestsBase {
Task t1 = createTaskBuilder("com.android.pkg1", ".Task1").build();
mRecentTasks.add(t1);
mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".HomeTask")
- .setParentTask(mTaskContainer.getRootHomeTask()).build());
+ .setParentTaskFragment(mTaskContainer.getRootHomeTask()).build());
Task t2 = createTaskBuilder("com.android.pkg2", ".Task2").build();
mRecentTasks.add(t2);
mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".PipTask")
@@ -1194,7 +1244,8 @@ public class RecentTasksTest extends WindowTestsBase {
}
return new TaskSnapshot(1, new ComponentName("", ""), buffer,
ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
- Surface.ROTATION_0, taskSize, new Rect() /* insets */, false /* isLowResolution */,
+ Surface.ROTATION_0, taskSize, new Rect() /* contentInsets */,
+ new Rect() /* letterboxInsets*/, false /* isLowResolution */,
true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */,
false /* isTranslucent */, false /* hasImeSurface */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index d88fbee6ae13..b4c449a94a40 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -41,8 +41,8 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -124,7 +124,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
// Verify that the finish callback to reparent the leash is called
verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter));
// Verify the animation canceled callback to the app was made
- verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */);
+ verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
verifyNoMoreInteractionsExceptAsBinder(mMockRunner);
}
@@ -207,7 +207,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
wallpaperWindowToken.cancelAnimation();
assertTrue(mController.isAnimatingTask(activity.getTask()));
assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
- verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */);
+ verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */,
+ null /* taskSnapshots */);
}
@Test
@@ -254,10 +255,10 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
mController.setDeferredCancel(true /* deferred */, false /* screenshot */);
mController.cancelAnimationWithScreenshot(false /* screenshot */);
- verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */);
+ verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
// Simulate the app transition finishing
- mController.mAppTransitionListener.onAppTransitionStartingLocked(false, 0, 0, 0);
+ mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0);
verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
}
@@ -281,7 +282,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
mController.setDeferredCancel(true /* deferred */, true /* screenshot */);
mController.cancelAnimationWithScreenshot(true /* screenshot */);
- verify(mMockRunner).onAnimationCanceled(mMockTaskSnapshot /* taskSnapshot */);
+ verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */,
+ any(TaskSnapshot[].class) /* taskSnapshots */);
// Continue the animation (simulating a call to cleanupScreenshot())
mController.continueDeferredCancelAnimation();
@@ -322,7 +324,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
mController.cancelAnimationWithScreenshot(true /* screenshot */);
- verify(mMockRunner).onAnimationCanceled(any());
+ verify(mMockRunner).onAnimationCanceled(any(), any());
// Simulate process crashing and ensure the animation is still canceled
mController.binderDied();
@@ -439,6 +441,22 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
}
@Test
+ public void testCheckRotationAfterCleanup() {
+ mWm.setRecentsAnimationController(mController);
+ spyOn(mDisplayContent.mFixedRotationTransitionListener);
+ doReturn(true).when(mDisplayContent.mFixedRotationTransitionListener)
+ .isTopFixedOrientationRecentsAnimating();
+ // Rotation update is skipped while the recents animation is running.
+ assertFalse(mDisplayContent.getDisplayRotation().updateOrientation(DisplayContentTests
+ .getRotatedOrientation(mDefaultDisplay), false /* forceUpdate */));
+ final int prevRotation = mDisplayContent.getRotation();
+ mWm.cleanupRecentsAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION);
+ waitHandlerIdle(mWm.mH);
+ // The display should be updated to the changed orientation after the animation is finished.
+ assertNotEquals(mDisplayContent.getRotation(), prevRotation);
+ }
+
+ @Test
public void testWallpaperHasFixedRotationApplied() {
unblockDisplayRotation(mDefaultDisplay);
mWm.setRecentsAnimationController(mController);
@@ -684,7 +702,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
mController.setWillFinishToHome(true);
mController.cancelAnimationForDisplayChange();
- verify(mMockRunner).onAnimationCanceled(any());
+ verify(mMockRunner).onAnimationCanceled(any(), any());
verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false);
}
@@ -699,7 +717,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
mController.setWillFinishToHome(false);
mController.cancelAnimationForDisplayChange();
- verify(mMockRunner).onAnimationCanceled(any());
+ verify(mMockRunner).onAnimationCanceled(any(), any());
verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false);
}
@@ -719,7 +737,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(),
anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */);
mController.cancelAnimationForHomeStart();
- verify(mMockRunner).onAnimationCanceled(any());
+ verify(mMockRunner).onAnimationCanceled(any(), any());
// Continue the animation (simulating a call to cleanupScreenshot())
mController.continueDeferredCancelAnimation();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index d017c19cac86..1b19a28a9790 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -31,8 +31,8 @@ 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.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index b6cfa8e96a4c..575e0820eca9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -36,12 +36,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
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;
@@ -51,6 +53,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
@@ -274,6 +277,32 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
@Test
+ public void testOpeningTaskWithTopFinishingActivity() {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "win");
+ final Task task = win.getTask();
+ final ActivityRecord topFinishing = new ActivityBuilder(mAtm).setTask(task).build();
+ // Now the task contains:
+ // - Activity[1] (top, finishing, no window)
+ // - Activity[0] (has window)
+ topFinishing.finishing = true;
+ spyOn(mDisplayContent.mAppTransition);
+ doReturn(mController).when(mDisplayContent.mAppTransition).getRemoteAnimationController();
+ task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN,
+ false /* isVoiceInteraction */, null /* sources */);
+ mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ try {
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+ appsCaptor.capture(), any(), any(), any());
+ } catch (RemoteException ignored) {
+ }
+ assertEquals(1, appsCaptor.getValue().length);
+ assertEquals(RemoteAnimationTarget.MODE_OPENING, appsCaptor.getValue()[0].mode);
+ }
+
+ @Test
public void testChangeToSmallerSize() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mChangingContainers.add(win.mActivityRecord);
@@ -314,7 +343,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
verify(mMockTransaction).setWindowCrop(
mMockLeash, app.startBounds.width(), app.startBounds.height());
verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
- verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, -1, -1);
+ verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(),
+ app.startBounds.height());
finishedCaptor.getValue().onAnimationFinished();
verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
@@ -367,7 +397,63 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
verify(mMockTransaction).setWindowCrop(
mMockLeash, app.startBounds.width(), app.startBounds.height());
verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
- verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, -1, -1);
+ verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(),
+ app.startBounds.height());
+
+ finishedCaptor.getValue().onAnimationFinished();
+ verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
+ eq(record.mAdapter));
+ verify(mThumbnailFinishedCallback).onAnimationFinished(
+ eq(ANIMATION_TYPE_WINDOW_ANIMATION), eq(record.mThumbnailAdapter));
+ } finally {
+ mDisplayContent.mChangingContainers.clear();
+ }
+ }
+
+ @Test
+ public void testChangeToDifferentPosition() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mChangingContainers.add(win.mActivityRecord);
+ try {
+ final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
+ win.mActivityRecord, new Point(100, 100), null, new Rect(150, 150, 400, 400),
+ new Rect(50, 100, 150, 150));
+ assertNotNull(record.mThumbnailAdapter);
+ ((AnimationAdapter) record.mAdapter)
+ .startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
+ mFinishedCallback);
+ ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash,
+ mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback);
+ mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE),
+ appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+ finishedCaptor.capture());
+ assertEquals(1, appsCaptor.getValue().length);
+ final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+ assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
+ assertEquals(new Point(100, 100), app.position);
+ assertEquals(new Rect(150, 150, 400, 400), app.sourceContainerBounds);
+ assertEquals(new Rect(50, 100, 150, 150), app.startBounds);
+ assertEquals(mMockLeash, app.leash);
+ assertEquals(mMockThumbnailLeash, app.startLeash);
+ assertEquals(false, app.isTranslucent);
+ verify(mMockTransaction).setPosition(
+ mMockLeash, app.position.x + app.startBounds.left - app.screenSpaceBounds.left,
+ app.position.y + app.startBounds.top - app.screenSpaceBounds.top);
+ verify(mMockTransaction).setWindowCrop(
+ mMockLeash, app.startBounds.width(), app.startBounds.height());
+ verify(mMockTransaction).setPosition(mMockThumbnailLeash, 0, 0);
+ verify(mMockTransaction).setWindowCrop(mMockThumbnailLeash, app.startBounds.width(),
+ app.startBounds.height());
finishedCaptor.getValue().onAnimationFinished();
verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_WINDOW_ANIMATION),
@@ -615,6 +701,51 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
}
+ @UseTestDisplay(addWindows = W_INPUT_METHOD)
+ @Test
+ public void testLaunchRemoteAnimationWithoutImeBehind() {
+ final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
+ final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
+
+ // Simulating win1 has shown IME and being IME layering/input target
+ mDisplayContent.setImeLayeringTarget(win1);
+ mDisplayContent.setImeInputTarget(win1);
+ mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
+ spyOn(mDisplayContent);
+ doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
+ doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
+ .prepareToShowInTransaction(any(), anyFloat());
+ makeWindowVisibleAndDrawn(mImeWindow);
+ assertTrue(mImeWindow.isOnScreen());
+ assertFalse(mImeWindow.isParentWindowHidden());
+
+ try {
+ // Simulating now win1 is being covered by the lockscreen which has no surface,
+ // and then launching an activity win2 with the remote animation
+ win1.mHasSurface = false;
+ mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win2.mActivityRecord, new Point(50, 100), null,
+ new Rect(50, 100, 150, 150), null).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+
+ mDisplayContent.applySurfaceChangesTransaction();
+ mController.goodToGo(TRANSIT_OLD_TASK_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN),
+ any(), any(), any(), any());
+ // Verify the IME window won't apply surface change transaction with forAllImeWindows
+ verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true));
+ } catch (Exception e) {
+ // no-op
+ } finally {
+ mDisplayContent.mOpeningApps.clear();
+ }
+ }
+
private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 0c6545c2438f..030733bf4424 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -30,28 +30,28 @@ import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+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.FINISHING;
+import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
-import static com.android.server.wm.Task.ActivityState.DESTROYED;
-import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.android.server.wm.TaskDisplayArea.getRootTaskAbove;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -89,9 +89,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
/**
* Tests for the root {@link Task} behavior.
*
@@ -194,7 +191,6 @@ public class RootTaskTests extends WindowTestsBase {
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
// Root task removal is deferred if one of its child is animating.
- doReturn(true).when(rootTask).hasWindowsAlive();
doReturn(rootTask).when(task).getAnimatingContainer(
eq(TRANSITION | CHILDREN), anyInt());
@@ -280,11 +276,11 @@ public class RootTaskTests extends WindowTestsBase {
public void testResumedActivity() {
final ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
final Task task = r.getTask();
- assertNull(task.getResumedActivity());
+ assertNull(task.getTopResumedActivity());
r.setState(RESUMED, "testResumedActivity");
- assertEquals(r, task.getResumedActivity());
+ assertEquals(r, task.getTopResumedActivity());
r.setState(PAUSING, "testResumedActivity");
- assertNull(task.getResumedActivity());
+ assertNull(task.getTopResumedActivity());
}
@Test
@@ -295,15 +291,15 @@ public class RootTaskTests extends WindowTestsBase {
final Task task = r.getTask();
// Ensure moving task between two root tasks updates resumed activity
r.setState(RESUMED, "testResumedActivityFromTaskReparenting");
- assertEquals(r, rootTask.getResumedActivity());
+ assertEquals(r, rootTask.getTopResumedActivity());
final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
task.reparent(destRootTask, true /* toTop */, REPARENT_KEEP_ROOT_TASK_AT_FRONT,
false /* animate */, true /* deferResume*/,
"testResumedActivityFromTaskReparenting");
- assertNull(rootTask.getResumedActivity());
- assertEquals(r, destRootTask.getResumedActivity());
+ assertNull(rootTask.getTopResumedActivity());
+ assertEquals(r, destRootTask.getTopResumedActivity());
}
@Test
@@ -314,15 +310,15 @@ public class RootTaskTests extends WindowTestsBase {
final Task task = r.getTask();
// Ensure moving task between two root tasks updates resumed activity
r.setState(RESUMED, "testResumedActivityFromActivityReparenting");
- assertEquals(r, rootTask.getResumedActivity());
+ assertEquals(r, rootTask.getTopResumedActivity());
final Task destRootTask = new TaskBuilder(mSupervisor).setOnTop(true).build();
task.reparent(destRootTask, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
false /* animate */, false /* deferResume*/,
"testResumedActivityFromActivityReparenting");
- assertNull(rootTask.getResumedActivity());
- assertEquals(r, destRootTask.getResumedActivity());
+ assertNull(rootTask.getTopResumedActivity());
+ assertEquals(r, destRootTask.getTopResumedActivity());
}
@Test
@@ -333,7 +329,7 @@ public class RootTaskTests extends WindowTestsBase {
// Create primary splitscreen root task.
final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor)
- .setParentTask(organizer.mPrimary)
+ .setParentTaskFragment(organizer.mPrimary)
.setOnTop(true)
.build();
@@ -509,8 +505,8 @@ public class RootTaskTests extends WindowTestsBase {
targetActivity);
final ComponentName alias = new ComponentName(DEFAULT_COMPONENT_PACKAGE_NAME,
aliasActivity);
- final Task parentTask = new TaskBuilder(mAtm.mTaskSupervisor).build();
- final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setParentTask(parentTask).build();
+ final Task parentTask = new TaskBuilder(mSupervisor).build();
+ final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(parentTask).build();
task.origActivity = alias;
task.realActivity = target;
new ActivityBuilder(mAtm).setComponent(target).setTask(task).setTargetActivity(
@@ -618,9 +614,9 @@ public class RootTaskTests extends WindowTestsBase {
doReturn(false).when(splitScreenSecondary2).isTranslucent(any());
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
// First split-screen secondary should be visible behind another translucent split-screen
@@ -628,9 +624,9 @@ public class RootTaskTests extends WindowTestsBase {
doReturn(true).when(splitScreenSecondary2).isTranslucent(any());
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitScreenSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
@@ -642,13 +638,13 @@ public class RootTaskTests extends WindowTestsBase {
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
assistantRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
// Split-screen root tasks should be visible behind a translucent fullscreen root task.
@@ -657,13 +653,13 @@ public class RootTaskTests extends WindowTestsBase {
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
assistantRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitScreenPrimary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitScreenSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitScreenSecondary2.getVisibility(null /* starting */));
// Assistant root task shouldn't be visible behind translucent split-screen root task,
@@ -678,25 +674,25 @@ public class RootTaskTests extends WindowTestsBase {
assertTrue(assistantRootTask.shouldBeVisible(null /* starting */));
assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
assistantRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
} else {
assertFalse(assistantRootTask.shouldBeVisible(null /* starting */));
assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
assistantRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
splitScreenPrimary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
splitScreenSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
splitScreenSecondary2.getVisibility(null /* starting */));
}
}
@@ -707,45 +703,51 @@ public class RootTaskTests extends WindowTestsBase {
WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */);
final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
+ // Creating as two-level tasks so home task can be reparented to split-secondary root task.
final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */);
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */,
+ true /* twoLevelTask */);
doReturn(false).when(homeRootTask).isTranslucent(any());
doReturn(false).when(splitPrimary).isTranslucent(any());
doReturn(false).when(splitSecondary).isTranslucent(any());
-
// Re-parent home to split secondary.
homeRootTask.reparent(splitSecondary, POSITION_TOP);
// Current tasks should be visible.
- assertEquals(TASK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ splitPrimary.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ splitSecondary.getVisibility(null /* starting */));
// Home task should still be visible even though it is a child of another visible task.
- assertEquals(TASK_VISIBILITY_VISIBLE, homeRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ homeRootTask.getVisibility(null /* starting */));
// Add fullscreen translucent task that partially occludes split tasks
final Task translucentRootTask = createStandardRootTaskForVisibilityTest(
WINDOWING_MODE_FULLSCREEN, true /* translucent */);
// Fullscreen translucent task should be visible
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
translucentRootTask.getVisibility(null /* starting */));
// Split tasks should be visible behind translucent
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitPrimary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
splitSecondary.getVisibility(null /* starting */));
// Home task should be visible behind translucent since its parent is visible behind
// translucent.
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
homeRootTask.getVisibility(null /* starting */));
// Hide split-secondary
splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */);
// Home split secondary and home task should be invisible.
- assertEquals(TASK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE, homeRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ splitSecondary.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ homeRootTask.getVisibility(null /* starting */));
}
@Test
@@ -757,9 +759,9 @@ public class RootTaskTests extends WindowTestsBase {
createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
bottomRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
translucentRootTask.getVisibility(null /* starting */));
}
@@ -775,10 +777,12 @@ public class RootTaskTests extends WindowTestsBase {
createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ bottomRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
translucentRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ opaqueRootTask.getVisibility(null /* starting */));
}
@Test
@@ -793,10 +797,11 @@ public class RootTaskTests extends WindowTestsBase {
createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- assertEquals(TASK_VISIBILITY_INVISIBLE, bottomRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
+ bottomRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
opaqueRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
translucentRootTask.getVisibility(null /* starting */));
}
@@ -809,9 +814,9 @@ public class RootTaskTests extends WindowTestsBase {
createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
true /* translucent */);
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
bottomTranslucentRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
translucentRootTask.getVisibility(null /* starting */));
}
@@ -824,9 +829,10 @@ public class RootTaskTests extends WindowTestsBase {
createStandardRootTaskForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
false /* translucent */);
- assertEquals(TASK_VISIBILITY_INVISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE,
bottomTranslucentRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE, opaqueRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ opaqueRootTask.getVisibility(null /* starting */));
}
@Test
@@ -840,16 +846,17 @@ public class RootTaskTests extends WindowTestsBase {
final Task pinnedRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea,
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
bottomRootTask.getVisibility(null /* starting */));
- assertEquals(TASK_VISIBILITY_VISIBLE,
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
translucentRootTask.getVisibility(null /* starting */));
// Add an activity to the pinned root task so it isn't considered empty for visibility
// check.
final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
.setTask(pinnedRootTask)
.build();
- assertEquals(TASK_VISIBILITY_VISIBLE, pinnedRootTask.getVisibility(null /* starting */));
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE,
+ pinnedRootTask.getVisibility(null /* starting */));
}
@Test
@@ -1142,12 +1149,12 @@ public class RootTaskTests extends WindowTestsBase {
} else if (twoLevelTask) {
task = new TaskBuilder(mSupervisor)
.setTaskDisplayArea(taskDisplayArea)
- .setWindowingMode(windowingMode)
.setActivityType(activityType)
.setOnTop(onTop)
.setCreateActivity(true)
.setCreateParentTask(true)
.build().getRootTask();
+ task.setWindowingMode(windowingMode);
} else {
task = new TaskBuilder(mSupervisor)
.setTaskDisplayArea(taskDisplayArea)
@@ -1301,9 +1308,9 @@ public class RootTaskTests extends WindowTestsBase {
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
- task.startPausingLocked(false /* uiSleeping */, topActivity,
+ task.startPausing(false /* uiSleeping */, topActivity,
"test");
- verify(task).completePauseLocked(anyBoolean(), eq(topActivity));
+ verify(task).completePause(anyBoolean(), eq(topActivity));
}
@Test
@@ -1494,41 +1501,6 @@ public class RootTaskTests extends WindowTestsBase {
}
@Test
- public void testIterateOccludedActivity() {
- final ArrayList<ActivityRecord> occludedActivities = new ArrayList<>();
- final Consumer<ActivityRecord> handleOccludedActivity = occludedActivities::add;
- final Task task = new TaskBuilder(mSupervisor).build();
- final ActivityRecord bottomActivity = new ActivityBuilder(mAtm).setTask(task).build();
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
- // Top activity occludes bottom activity.
- doReturn(true).when(task).shouldBeVisible(any());
- assertTrue(topActivity.shouldBeVisible());
- assertFalse(bottomActivity.shouldBeVisible());
-
- task.forAllOccludedActivities(handleOccludedActivity);
- assertThat(occludedActivities).containsExactly(bottomActivity);
-
- // Top activity doesn't occlude parent, so the bottom activity is not occluded.
- doReturn(false).when(topActivity).occludesParent();
- assertTrue(bottomActivity.shouldBeVisible());
-
- occludedActivities.clear();
- task.forAllOccludedActivities(handleOccludedActivity);
- assertThat(occludedActivities).isEmpty();
-
- // A finishing activity should not occlude other activities behind.
- final ActivityRecord finishingActivity = new ActivityBuilder(mAtm).setTask(task).build();
- finishingActivity.finishing = true;
- doCallRealMethod().when(finishingActivity).occludesParent();
- assertTrue(topActivity.shouldBeVisible());
- assertTrue(bottomActivity.shouldBeVisible());
-
- occludedActivities.clear();
- task.forAllOccludedActivities(handleOccludedActivity);
- assertThat(occludedActivities).isEmpty();
- }
-
- @Test
public void testClearUnknownAppVisibilityBehindFullscreenActivity() {
final UnknownAppVisibilityController unknownAppVisibilityController =
mDefaultTaskDisplayArea.mDisplayContent.mUnknownAppVisibilityController;
@@ -1544,7 +1516,7 @@ public class RootTaskTests extends WindowTestsBase {
activities[i] = r;
doReturn(null).when(mAtm).getProcessController(
eq(r.processName), eq(r.info.applicationInfo.uid));
- r.setState(Task.ActivityState.INITIALIZING, "test");
+ r.setState(INITIALIZING, "test");
// Ensure precondition that the activity is opaque.
assertTrue(r.occludesParent());
mSupervisor.startSpecificActivity(r, false /* andResume */,
@@ -1552,14 +1524,13 @@ public class RootTaskTests extends WindowTestsBase {
}
mSupervisor.endDeferResume();
- setBooted(mAtm);
// 2 activities are started while keyguard is locked, so they are waiting to be resolved.
assertFalse(unknownAppVisibilityController.allResolved());
- // Assume the top activity is going to resume and
- // {@link RootWindowContainer#cancelInitializingActivities} should clear the unknown
- // visibility records that are occluded.
- task.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
+ // Any common path that updates activity visibility should clear the unknown visibility
+ // records that are no longer visible according to hierarchy.
+ task.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
+ false /* preserveWindows */);
// Assume the top activity relayouted, just remove it directly.
unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
// All unresolved records should be removed.
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 9cf29d4dcc50..4069f0f41d90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -31,13 +31,14 @@ 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.spyOn;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
+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;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
-import static com.android.server.wm.Task.ActivityState.FINISHING;
-import static com.android.server.wm.Task.ActivityState.PAUSED;
-import static com.android.server.wm.Task.ActivityState.PAUSING;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
-import static com.android.server.wm.Task.ActivityState.STOPPING;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.google.common.truth.Truth.assertThat;
@@ -167,7 +168,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
@Test
public void testTaskLayerRank() {
final Task rootTask = new TaskBuilder(mSupervisor).build();
- final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
new ActivityBuilder(mAtm).setTask(task1).build().mVisibleRequested = true;
mWm.mRoot.rankTaskLayers();
@@ -365,7 +366,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
TaskDisplayArea defaultTaskDisplayArea = display.getDefaultTaskDisplayArea();
doReturn(isFocusedTask ? task : null).when(defaultTaskDisplayArea).getFocusedRootTask();
mRootWindowContainer.applySleepTokens(true);
- verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleepingLocked();
+ verify(task, times(expectWakeFromSleep ? 1 : 0)).awakeFromSleeping();
verify(task, times(expectResumeTopActivity ? 1 : 0)).resumeTopActivityUncheckedLocked(
null /* target */, null /* targetOptions */);
}
@@ -386,7 +387,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
// landscape and the portrait lockscreen is shown.
activity.setLastReportedConfiguration(
new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
- activity.setState(Task.ActivityState.STOPPED, "sleep");
+ activity.setState(STOPPED, "sleep");
display.setIsSleeping(true);
doReturn(false).when(display).shouldSleep();
@@ -522,7 +523,8 @@ public class RootWindowContainerTests extends WindowTestsBase {
final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
final Task targetRootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, false /* onTop */);
- final Task targetTask = new TaskBuilder(mSupervisor).setParentTask(targetRootTask).build();
+ final Task targetTask = new TaskBuilder(mSupervisor).setParentTaskFragment(targetRootTask)
+ .build();
// Create Recents on secondary display.
final TestDisplayContent secondDisplay = addNewDisplayContentAt(
@@ -557,8 +559,8 @@ public class RootWindowContainerTests extends WindowTestsBase {
doReturn(rootTask).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
// Use the task as target to resume.
- mRootWindowContainer.resumeFocusedTasksTopActivities(
- rootTask, activity, null /* targetOptions */);
+ mRootWindowContainer.resumeFocusedTasksTopActivities(rootTask, activity,
+ null /* targetOptions */);
// Verify the target task should resume its activity.
verify(rootTask, times(1)).resumeTopActivityUncheckedLocked(
@@ -626,7 +628,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
ACTIVITY_TYPE_STANDARD, false /* onTop */));
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setTask(rootTask).setOnTop(true).build();
- activity.setState(Task.ActivityState.RESUMED, "test");
+ activity.setState(RESUMED, "test");
// Assume the task is at the topmost position
assertTrue(rootTask.isTopRootTaskInDisplayArea());
@@ -646,7 +648,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
ACTIVITY_TYPE_STANDARD, false /* onTop */));
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setTask(rootTask).setOnTop(true).build();
- activity.setState(Task.ActivityState.RESUMED, "test");
+ activity.setState(RESUMED, "test");
taskDisplayArea.positionChildAt(POSITION_BOTTOM, rootTask, false /*includingParents*/);
// Assume the task is at the topmost position
@@ -774,7 +776,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
}
/**
- * Tests that when starting {@link #ResolverActivity} for home, it should use the standard
+ * Tests that when starting {@link ResolverActivity} for home, it should use the standard
* activity type (in a new root task) so the order of back stack won't be broken.
*/
@Test
@@ -1006,33 +1008,31 @@ public class RootWindowContainerTests extends WindowTestsBase {
@Test
public void testLockAllProfileTasks() {
- // Make an activity visible with the user id set to 0
- final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
- final int taskId = task.mTaskId;
- final ActivityRecord activity = task.getTopMostActivity();
-
- // Create another activity on top and the user id is 1
- final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task)
- .setUid(UserHandle.PER_USER_RANGE + 1).build();
- doReturn(true).when(topActivity).okToShowLocked();
+ final int profileUid = UserHandle.PER_USER_RANGE + UserHandle.MIN_SECONDARY_USER_ID;
+ final int profileUserId = UserHandle.getUserId(profileUid);
+ // Create an activity belonging to the profile user.
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setUid(profileUid).build();
+ final Task task = activity.getTask();
+
+ // Create another activity belonging to current user on top.
+ final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.intent.setAction(Intent.ACTION_MAIN);
// Make sure the listeners will be notified for putting the task to locked state
TaskChangeNotificationController controller = mAtm.getTaskChangeNotificationController();
spyOn(controller);
- mWm.mRoot.lockAllProfileTasks(0);
- verify(controller).notifyTaskProfileLocked(eq(taskId), eq(0));
+ mWm.mRoot.lockAllProfileTasks(profileUserId);
+ verify(controller).notifyTaskProfileLocked(eq(task.mTaskId), eq(profileUserId));
// Create the work lock activity on top of the task
- final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task)
- .setUid(UserHandle.PER_USER_RANGE + 1).build();
- doReturn(true).when(workLockActivity).okToShowLocked();
+ final ActivityRecord workLockActivity = new ActivityBuilder(mAtm).setTask(task).build();
workLockActivity.intent.setAction(ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER);
doReturn(workLockActivity.mActivityComponent).when(mAtm).getSysUiServiceComponentLocked();
// Make sure the listener won't be notified again.
clearInvocations(controller);
- mWm.mRoot.lockAllProfileTasks(0);
+ mWm.mRoot.lockAllProfileTasks(profileUserId);
verify(controller, never()).notifyTaskProfileLocked(anyInt(), anyInt());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index c44c22fefd7a..cb858845e03e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -166,7 +166,7 @@ public class RunningTasksTest extends WindowTestsBase {
final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
.setComponent(new ComponentName(mContext.getPackageName(), className))
.setTaskId(taskId)
- .setParentTask(stack)
+ .setParentTaskFragment(stack)
.build();
task.lastActiveTime = lastActiveTime;
final ActivityRecord activity = new ActivityBuilder(mAtm)
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 f35e85c3b14c..9639aa78fd5b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -40,8 +40,15 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
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.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;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
+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.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.Task.ActivityState.STOPPED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -53,7 +60,9 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
@@ -74,6 +83,8 @@ import android.view.WindowManager;
import androidx.test.filters.MediumTest;
+import com.android.internal.policy.SystemBarUtils;
+
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -99,10 +110,14 @@ public class SizeCompatTests extends WindowTestsBase {
private Task mTask;
private ActivityRecord mActivity;
+ private ActivityMetricsLogger mActivityMetricsLogger;
private Properties mInitialConstrainDisplayApisFlags;
@Before
public void setUp() throws Exception {
+ mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
+ clearInvocations(mActivityMetricsLogger);
+ doReturn(mActivityMetricsLogger).when(mAtm.mTaskSupervisor).getActivityMetricsLogger();
mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties(
NAMESPACE_CONSTRAIN_DISPLAY_APIS);
DeviceConfig.setProperties(
@@ -130,7 +145,7 @@ public class SizeCompatTests extends WindowTestsBase {
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
mActivity.mVisibleRequested = true;
mActivity.setSavedState(null /* savedState */);
- mActivity.setState(Task.ActivityState.RESUMED, "testRestart");
+ mActivity.setState(RESUMED, "testRestart");
prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
@@ -138,49 +153,11 @@ public class SizeCompatTests extends WindowTestsBase {
// The visible activity should recompute configuration according to the last parent bounds.
mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.appToken);
- assertEquals(Task.ActivityState.RESTARTING_PROCESS, mActivity.getState());
+ assertEquals(RESTARTING_PROCESS, mActivity.getState());
assertNotEquals(originalOverrideBounds, mActivity.getBounds());
}
@Test
- public void testKeepBoundsWhenChangingFromFreeformToFullscreen() {
- removeGlobalMinSizeRestriction();
- // Create landscape freeform display and a freeform app.
- DisplayContent display = new TestDisplayContent.Builder(mAtm, 2000, 1000)
- .setCanRotate(false)
- .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM).build();
- setUpApp(display);
-
- // Put app window into portrait freeform and then make it a compat app.
- final Rect bounds = new Rect(100, 100, 400, 600);
- mTask.setBounds(bounds);
- prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(bounds, mActivity.getBounds());
- // Activity is not yet in size compat mode; it is filling the freeform task window.
- assertActivityMaxBoundsSandboxed();
-
- // The activity should be able to accept negative x position [-150, 100 - 150, 600].
- final int dx = bounds.left + bounds.width() / 2;
- mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom);
- assertEquals(mTask.getBounds(), mActivity.getBounds());
-
- final int density = mActivity.getConfiguration().densityDpi;
-
- // Change display configuration to fullscreen.
- Configuration c = new Configuration(display.getRequestedOverrideConfiguration());
- c.windowConfiguration.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- display.onRequestedOverrideConfigurationChanged(c);
-
- // Check if dimensions on screen stay the same by scaling.
- assertScaled();
- assertEquals(bounds.width(), mActivity.getBounds().width());
- assertEquals(bounds.height(), mActivity.getBounds().height());
- assertEquals(density, mActivity.getConfiguration().densityDpi);
- // Size compat mode is sandboxed at the activity level.
- assertActivityMaxBoundsSandboxed();
- }
-
- @Test
public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() {
final int notchHeight = 100;
setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build());
@@ -318,6 +295,11 @@ public class SizeCompatTests extends WindowTestsBase {
assertScaled();
// Activity is sandboxed due to size compat mode.
assertActivityMaxBoundsSandboxed();
+
+ final WindowState appWindow = addWindowToActivity(mActivity);
+ assertTrue(mActivity.hasSizeCompatBounds());
+ assertEquals("App window must use size compat bounds for layout in screen space",
+ mActivity.getBounds(), appWindow.getBounds());
}
@Test
@@ -585,7 +567,7 @@ public class SizeCompatTests extends WindowTestsBase {
public void testHandleActivitySizeCompatModeChanged() {
setUpDisplaySizeWithApp(1000, 2000);
doReturn(true).when(mTask).isOrganized();
- mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+ mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
@@ -604,7 +586,7 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mVisibleRequested = true;
mActivity.restartProcessIfVisible();
// The full lifecycle isn't hooked up so manually set state to resumed
- mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+ mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity);
// Expect null token when switching to non-size-compat mode activity.
@@ -618,7 +600,7 @@ public class SizeCompatTests extends WindowTestsBase {
public void testHandleActivitySizeCompatModeChangedOnDifferentTask() {
setUpDisplaySizeWithApp(1000, 2000);
doReturn(true).when(mTask).isOrganized();
- mActivity.setState(Task.ActivityState.RESUMED, "testHandleActivitySizeCompatModeChanged");
+ mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
@@ -637,7 +619,7 @@ public class SizeCompatTests extends WindowTestsBase {
.setCreateActivity(true).build();
final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity();
doReturn(true).when(secondTask).isOrganized();
- secondActivity.setState(Task.ActivityState.RESUMED,
+ secondActivity.setState(RESUMED,
"testHandleActivitySizeCompatModeChanged");
prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
@@ -665,7 +647,7 @@ public class SizeCompatTests extends WindowTestsBase {
.setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
.setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
.build();
- assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateCompatDisplayInsets());
// The non-resizable activity should not be size compat because it is on a resizable task
// in multi-window mode.
@@ -697,7 +679,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- public void testShouldCreateCompatDisplayInsetsWhenUnresizeableAndSupportsSizeChangesFalse() {
+ public void testShouldNotCreateCompatDisplayInsetsWhenRootActivityIsResizeable() {
setUpDisplaySizeWithApp(1000, 2500);
// Make the task root resizable.
@@ -706,7 +688,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Create an activity on the same task.
final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(activity.shouldCreateCompatDisplayInsets());
+ assertFalse(activity.shouldCreateCompatDisplayInsets());
}
@Test
@@ -1101,6 +1083,147 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+ 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 = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override should have no effect
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1000, activity.getBounds().width());
+
+ // After changing the orientation to portrait the override should be applied.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ activity.clearSizeCompatMode();
+
+ // The per-package override forces the activity into a 3:2 aspect ratio
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+ 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 = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override should have no effect
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1000, activity.getBounds().width());
+
+ // After changing the orientation to portrait the override should be applied.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ activity.clearSizeCompatMode();
+
+ // The per-package override forces the activity into a 3:2 aspect ratio
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ 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 = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override forces the activity into a 3:2 aspect ratio
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ activity.getBounds().width(), 0.5);
+
+ // After changing the orientation to landscape the override shouldn't be applied.
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ activity.clearSizeCompatMode();
+
+ // The per-package override should have no effect
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1000, activity.getBounds().width());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ 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 = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override forces the activity into a 3:2 aspect ratio
+ assertEquals(1200, activity.getBounds().height());
+ assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+ @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
+ 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 = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ // The per-package override forces the activity into a 3:2 aspect ratio
+ assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+ activity.getBounds().height(), 0.5);
+ assertEquals(1000, activity.getBounds().width());
+ }
+
+ @Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioWithoutGlobalOverride() {
// In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
@@ -1553,6 +1676,30 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
+ // Set up a display in landscape with an unresizable app.
+ setUpDisplaySizeWithApp(2500, 1000);
+ mActivity.mDisplayContent.setSandboxDisplayApis(false /* sandboxDisplayApis */);
+ prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
+ assertFitted();
+
+ // Activity max bounds not be sandboxed since sandboxing is disabled.
+ assertMaxBoundsInheritDisplayAreaBounds();
+ }
+
+ @Test
+ public void testSandboxDisplayApis_unresizableAppSandboxed() {
+ // Set up a display in landscape with an unresizable app.
+ setUpDisplaySizeWithApp(2500, 1000);
+ mActivity.mDisplayContent.setSandboxDisplayApis(true /* sandboxDisplayApis */);
+ prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
+ assertFitted();
+
+ // Activity max bounds should be sandboxed since sandboxing is enabled.
+ assertActivityMaxBoundsSandboxed();
+ }
+
+ @Test
public void testResizableApp_notSandboxed() {
// Set up a display in landscape with a fully resizable app.
setUpDisplaySizeWithApp(2500, 1000);
@@ -1808,7 +1955,7 @@ public class SizeCompatTests extends WindowTestsBase {
// At launch.
/* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
+ /* sizeCompatUnscaled */ new Rect(0, 700, 700, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(0, 0, 350, 700));
}
@@ -1821,7 +1968,7 @@ public class SizeCompatTests extends WindowTestsBase {
// At launch.
/* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(525, 0, 875, 700));
}
@@ -1836,7 +1983,7 @@ public class SizeCompatTests extends WindowTestsBase {
// At launch.
/* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(525, 0, 875, 700));
@@ -1846,7 +1993,7 @@ public class SizeCompatTests extends WindowTestsBase {
// At launch.
/* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ /* sizeCompatUnscaled */ new Rect(350, 700, 1050, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(525, 0, 875, 700));
}
@@ -1859,7 +2006,7 @@ public class SizeCompatTests extends WindowTestsBase {
// At launch.
/* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
// After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
+ /* sizeCompatUnscaled */ new Rect(700, 700, 1400, 2100),
// After the display is resized to (700, 1400).
/* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
}
@@ -1908,13 +2055,19 @@ public class SizeCompatTests extends WindowTestsBase {
setUpDisplaySizeWithApp(1000, 2500);
assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
+
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
// After returning to the original rotation, bounds are computed in
@@ -1924,6 +2077,18 @@ public class SizeCompatTests extends WindowTestsBase {
assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
+
+ // After setting the visibility of the activity to false, areBoundsLetterboxed() still
+ // returns true but the NOT_VISIBLE App Compat state is logged.
+ mActivity.setVisibility(false);
+ assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE);
+ mActivity.setVisibility(true);
+ assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
}
@Test
@@ -1932,12 +2097,15 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION);
}
@Test
@@ -1947,12 +2115,70 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
assertFalse(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
assertTrue(mActivity.inSizeCompatMode());
assertTrue(mActivity.areBoundsLetterboxed());
+ verifyLogAppCompatState(mActivity,
+ APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
+ }
+
+ /**
+ * Tests that all three paths in which aspect ratio logic can be applied yield the same
+ * result, which is that aspect ratio is respected on app bounds. The three paths are
+ * fixed orientation, no fixed orientation but fixed aspect ratio, and size compat mode.
+ */
+ @Test
+ public void testAllAspectRatioLogicConsistent() {
+ // Create display that has all stable insets and does not rotate. Make sure that status bar
+ // height is greater than notch height so that stable bounds do not equal app bounds.
+ final int notchHeight = 75;
+ final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
+ .setSystemDecorations(true).setNotch(notchHeight)
+ .setStatusBarHeight(notchHeight + 20).setCanRotate(false).build();
+
+ // Create task on test display.
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
+
+ // Target min aspect ratio must be larger than parent aspect ratio to be applied.
+ final float targetMinAspectRatio = 3.0f;
+
+ // Create fixed portait activity with min aspect ratio greater than parent aspect ratio.
+ final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ // Create activity with no fixed orientation and min aspect ratio greater than parent aspect
+ // ratio.
+ final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm).setTask(task)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ // Create unresizeable fixed portait activity with min aspect ratio greater than parent
+ // aspect ratio.
+ final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
+ .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .setMinAspectRatio(targetMinAspectRatio).build();
+ // Resize display running unresizeable activity to make it enter size compat mode.
+ resizeDisplay(display, 1800, 1000);
+ final Rect sizeCompatAppBounds = new Rect(sizeCompatActivity.getConfiguration()
+ .windowConfiguration.getAppBounds());
+
+ // Check that aspect ratio of app bounds is equal to the min aspect ratio.
+ final float delta = 0.01f;
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(fixedOrientationAppBounds), delta);
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(minAspectRatioAppBounds), delta);
+ assertEquals(targetMinAspectRatio, ActivityRecord
+ .computeAspectRatio(sizeCompatAppBounds), delta);
}
private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -1972,7 +2198,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertTrue(mActivity.inSizeCompatMode());
// Activity is in size compat mode but not scaled.
- assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
+ assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds());
}
private static WindowState addWindowToActivity(ActivityRecord activity) {
@@ -2005,8 +2231,7 @@ public class SizeCompatTests extends WindowTestsBase {
displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
token.addWindow(statusBar);
statusBar.setRequestedSize(displayContent.mBaseDisplayWidth,
- displayContent.getDisplayUiContext().getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height));
+ SystemBarUtils.getStatusBarHeight(displayContent.getDisplayUiContext()));
displayPolicy.addWindowLw(statusBar, attrs);
displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames);
@@ -2053,6 +2278,12 @@ public class SizeCompatTests extends WindowTestsBase {
activity.info.resizeMode = isUnresizable
? RESIZE_MODE_UNRESIZEABLE
: RESIZE_MODE_RESIZEABLE;
+ final Task task = activity.getTask();
+ if (task != null) {
+ // Update the Task resize value as activity will follow the task.
+ task.mResizeMode = activity.info.resizeMode;
+ task.getRootActivity().info.resizeMode = activity.info.resizeMode;
+ }
activity.mVisibleRequested = true;
if (maxAspect >= 0) {
activity.info.setMaxAspectRatio(maxAspect);
@@ -2111,6 +2342,11 @@ public class SizeCompatTests extends WindowTestsBase {
.isEqualTo(activity.getWindowConfiguration().getBounds());
}
+ private void verifyLogAppCompatState(ActivityRecord activity, int state) {
+ verify(mActivityMetricsLogger, atLeastOnce()).logAppCompatState(
+ argThat(r -> activity == r && r.getAppCompatState() == state));
+ }
+
static Configuration rotateDisplay(DisplayContent display, int rotation) {
final Configuration c = new Configuration();
display.getDisplayRotation().setRotation(rotation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 3a2190d13354..cac948c97b25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -178,6 +178,11 @@ public class StubTransaction extends SurfaceControl.Transaction {
}
@Override
+ public SurfaceControl.Transaction setDisplayFlags(IBinder displayToken, int flags) {
+ return this;
+ }
+
+ @Override
public SurfaceControl.Transaction setDisplayProjection(IBinder displayToken,
int orientation, Rect layerStackRect, Rect displayRect) {
return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 7bac3e7b8679..420ea8e63562 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -62,7 +62,7 @@ public class SyncEngineTests extends WindowTestsBase {
public void testTrivialSyncCallback() {
TestWindowContainer mockWC = new TestWindowContainer(mWm, false /* waiter */);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -90,7 +90,7 @@ public class SyncEngineTests extends WindowTestsBase {
public void testWaitingSyncCallback() {
TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -114,7 +114,7 @@ public class SyncEngineTests extends WindowTestsBase {
public void testInvisibleSyncCallback() {
TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -142,7 +142,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(childWC, POSITION_TOP);
parentWC.addChild(childWC2, POSITION_TOP);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -175,7 +175,7 @@ public class SyncEngineTests extends WindowTestsBase {
TestWindowContainer childWC = new TestWindowContainer(mWm, true /* waiter */);
parentWC.addChild(childWC, POSITION_TOP);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -206,7 +206,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
parentWC.addChild(botChildWC, POSITION_BOTTOM);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -238,7 +238,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
parentWC.addChild(botChildWC, POSITION_BOTTOM);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -273,7 +273,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
nonMemberParentWC.addChild(botChildWC, POSITION_BOTTOM);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
@@ -312,7 +312,7 @@ public class SyncEngineTests extends WindowTestsBase {
parentWC.addChild(topChildWC, POSITION_TOP);
parentWC.addChild(botChildWC, POSITION_BOTTOM);
- BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine();
BLASTSyncEngine.TransactionReadyListener listener = mock(
BLASTSyncEngine.TransactionReadyListener.class);
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 8e7ba4bc3293..5bc45d7c3d17 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -271,7 +271,6 @@ public class SystemServicesTestRule implements TestRule {
doNothing().when(amInternal).cleanUpServices(anyInt(), any(), any());
doReturn(UserHandle.USER_SYSTEM).when(amInternal).getCurrentUserId();
doReturn(TEST_USER_PROFILE_IDS).when(amInternal).getCurrentProfileIds();
- doReturn(true).when(amInternal).isCurrentProfile(anyInt());
doReturn(true).when(amInternal).isUserRunning(anyInt(), anyInt());
doReturn(true).when(amInternal).hasStartedUserState(anyInt());
doReturn(false).when(amInternal).shouldConfirmCredentials(anyInt());
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 67b273a5a82d..cdf6b59d4737 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -37,8 +37,8 @@ import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
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.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -84,8 +84,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.mAdjacentTask = rootTask;
- rootTask.mAdjacentTask = adjacentRootTask;
+ adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -111,8 +110,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
final Task adjacentRootTask = createTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
- adjacentRootTask.mAdjacentTask = rootTask;
- rootTask.mAdjacentTask = adjacentRootTask;
+ adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
taskDisplayArea.setLaunchRootTask(rootTask,
new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -133,8 +131,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.mAdjacentTask = rootTask;
- rootTask.mAdjacentTask = adjacentRootTask;
+ adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -623,7 +620,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
final Task pinnedRootTask = mRootWindowContainer.getDefaultTaskDisplayArea()
.createRootTask(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, ON_TOP);
final Task pinnedTask = new TaskBuilder(mAtm.mTaskSupervisor)
- .setParentTask(pinnedRootTask).build();
+ .setParentTaskFragment(pinnedRootTask).build();
new ActivityBuilder(mAtm).setActivityFlags(FLAG_ALWAYS_FOCUSABLE)
.setTask(pinnedTask).build();
pinnedRootTask.moveToFront("movePinnedRootTaskToFront");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
new file mode 100644
index 000000000000..f8c7207cefa7
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -0,0 +1,526 @@
+/*
+ * 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.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.testing.Assert.assertThrows;
+
+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.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationDefinition;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentCreationParams;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizerToken;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransactionCallback;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:TaskFragmentOrganizerControllerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
+
+ private TaskFragmentOrganizerController mController;
+ private TaskFragmentOrganizer mOrganizer;
+ private TaskFragmentOrganizerToken mOrganizerToken;
+ private ITaskFragmentOrganizer mIOrganizer;
+ private TaskFragment mTaskFragment;
+ private TaskFragmentInfo mTaskFragmentInfo;
+ private IBinder mFragmentToken;
+ private WindowContainerTransaction mTransaction;
+ private WindowContainerToken mFragmentWindowToken;
+ private RemoteAnimationDefinition mDefinition;
+
+ @Before
+ public void setup() {
+ mController = mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController;
+ mOrganizer = new TaskFragmentOrganizer(Runnable::run);
+ mOrganizerToken = mOrganizer.getOrganizerToken();
+ mIOrganizer = ITaskFragmentOrganizer.Stub.asInterface(mOrganizerToken.asBinder());
+ mTaskFragmentInfo = mock(TaskFragmentInfo.class);
+ mFragmentToken = new Binder();
+ mTaskFragment =
+ new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */);
+ mTransaction = new WindowContainerTransaction();
+ mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
+ mDefinition = new RemoteAnimationDefinition();
+
+ spyOn(mController);
+ spyOn(mOrganizer);
+ spyOn(mTaskFragment);
+ doReturn(mIOrganizer).when(mTaskFragment).getTaskFragmentOrganizer();
+ doReturn(mTaskFragmentInfo).when(mTaskFragment).getTaskFragmentInfo();
+ doReturn(new SurfaceControl()).when(mTaskFragment).getSurfaceControl();
+ doReturn(mFragmentToken).when(mTaskFragment).getFragmentToken();
+ doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
+ }
+
+ @Test
+ public void testCallTaskFragmentCallbackWithoutRegister_throwsException() {
+ assertThrows(IllegalArgumentException.class, () -> mController
+ .onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment));
+
+ assertThrows(IllegalArgumentException.class, () -> mController
+ .onTaskFragmentInfoChanged(
+ mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment));
+
+ assertThrows(IllegalArgumentException.class, () -> mController
+ .onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment));
+
+ assertThrows(IllegalArgumentException.class, () -> mController
+ .onTaskFragmentParentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(),
+ mTaskFragment));
+ }
+
+ @Test
+ public void testOnTaskFragmentAppeared() {
+ mController.registerOrganizer(mIOrganizer);
+
+ mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer).onTaskFragmentAppeared(any());
+ }
+
+ @Test
+ public void testOnTaskFragmentInfoChanged() {
+ mController.registerOrganizer(mIOrganizer);
+ mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ // No callback if the info is not changed.
+ doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
+ doReturn(new Configuration()).when(mTaskFragmentInfo).getConfiguration();
+
+ mController.onTaskFragmentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(),
+ mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+
+ // Trigger callback if the info is changed.
+ doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
+
+ mController.onTaskFragmentInfoChanged(mTaskFragment.getTaskFragmentOrganizer(),
+ mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo);
+ }
+
+ @Test
+ public void testOnTaskFragmentVanished() {
+ mController.registerOrganizer(mIOrganizer);
+
+ mTaskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer).onTaskFragmentVanished(any());
+ }
+
+ @Test
+ public void testOnTaskFragmentParentInfoChanged() {
+ mController.registerOrganizer(mIOrganizer);
+ final Task parent = mock(Task.class);
+ final Configuration parentConfig = new Configuration();
+ parentConfig.smallestScreenWidthDp = 10;
+ doReturn(parent).when(mTaskFragment).getParent();
+ doReturn(parentConfig).when(parent).getConfiguration();
+ doReturn(parent).when(parent).asTask();
+
+ mTaskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentParentInfoChanged(
+ mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any());
+
+ // No extra callback if the info is not changed.
+ clearInvocations(mOrganizer);
+
+ mController.onTaskFragmentParentInfoChanged(
+ mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), any());
+
+ // Trigger callback if the info is changed.
+ parentConfig.smallestScreenWidthDp = 100;
+
+ mController.onTaskFragmentParentInfoChanged(
+ mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mFragmentToken), any());
+ }
+
+ @Test
+ public void testOnTaskFragmentError() throws RemoteException {
+ final IBinder errorCallbackToken = new Binder();
+ final Throwable exception = new IllegalArgumentException("Test exception");
+
+ mController.registerOrganizer(mIOrganizer);
+ mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
+ errorCallbackToken, exception);
+ mController.dispatchPendingEvents();
+
+ verify(mOrganizer).onTaskFragmentError(eq(errorCallbackToken), eq(exception));
+ }
+
+ @Test
+ public void testRegisterRemoteAnimations() {
+ mController.registerOrganizer(mIOrganizer);
+ mController.registerRemoteAnimations(mIOrganizer, mDefinition);
+
+ assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
+
+ mController.unregisterRemoteAnimations(mIOrganizer);
+
+ assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
+ }
+
+ @Test
+ public void testWindowContainerTransaction_setTaskFragmentOrganizer() {
+ mOrganizer.applyTransaction(mTransaction);
+
+ assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer());
+
+ mTransaction = new WindowContainerTransaction();
+ mOrganizer.applySyncTransaction(
+ mTransaction, mock(WindowContainerTransactionCallback.class));
+
+ assertEquals(mIOrganizer, mTransaction.getTaskFragmentOrganizer());
+ }
+
+ @Test
+ public void testApplyTransaction_enforceConfigurationChangeOnOrganizedTaskFragment()
+ throws RemoteException {
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Throw exception if the transaction is trying to change a window that is not organized by
+ // the organizer.
+ mTransaction.setBounds(mFragmentWindowToken, new Rect(0, 0, 100, 100));
+
+ assertThrows(SecurityException.class, () -> {
+ try {
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ } catch (RemoteException e) {
+ fail();
+ }
+ });
+
+ // Allow transaction to change a TaskFragment created by the organizer.
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ }
+
+ @Test
+ public void testApplyTransaction_enforceHierarchyChange_reorder() throws RemoteException {
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Throw exception if the transaction is trying to change a window that is not organized by
+ // the organizer.
+ mTransaction.reorder(mFragmentWindowToken, true /* onTop */);
+
+ assertThrows(SecurityException.class, () -> {
+ try {
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ } catch (RemoteException e) {
+ fail();
+ }
+ });
+
+ // Allow transaction to change a TaskFragment created by the organizer.
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ }
+
+ @Test
+ public void testApplyTransaction_enforceHierarchyChange_deleteTaskFragment()
+ throws RemoteException {
+ mController.registerOrganizer(mIOrganizer);
+ mOrganizer.applyTransaction(mTransaction);
+ doReturn(true).when(mTaskFragment).isAttached();
+
+ // Throw exception if the transaction is trying to change a window that is not organized by
+ // the organizer.
+ mTransaction.deleteTaskFragment(mFragmentWindowToken);
+
+ assertThrows(SecurityException.class, () -> {
+ try {
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ } catch (RemoteException e) {
+ fail();
+ }
+ });
+
+ // Allow transaction to change a TaskFragment created by the organizer.
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+ clearInvocations(mAtm.mRootWindowContainer);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ // No lifecycle update when the TaskFragment is not recorded.
+ verify(mAtm.mRootWindowContainer, never()).resumeFocusedTasksTopActivities();
+
+ mAtm.mWindowOrganizerController.mLaunchTaskFragments
+ .put(mFragmentToken, mTaskFragment);
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ }
+
+ @Test
+ public void testApplyTransaction_enforceHierarchyChange_setAdjacentRoots()
+ throws RemoteException {
+ final TaskFragment taskFragment2 =
+ new TaskFragment(mAtm, new Binder(), true /* createdByOrganizer */);
+ final WindowContainerToken token2 = taskFragment2.mRemoteToken.toWindowContainerToken();
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Throw exception if the transaction is trying to change a window that is not organized by
+ // the organizer.
+ mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */);
+
+ assertThrows(SecurityException.class, () -> {
+ try {
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ } catch (RemoteException e) {
+ fail();
+ }
+ });
+
+ // Allow transaction to change a TaskFragment created by the organizer.
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+ taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+ clearInvocations(mAtm.mRootWindowContainer);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ }
+
+ @Test
+ public void testApplyTransaction_enforceHierarchyChange_createTaskFragment()
+ throws RemoteException {
+ mController.registerOrganizer(mIOrganizer);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token).build();
+ mOrganizer.applyTransaction(mTransaction);
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ mTransaction.createTaskFragment(params);
+ mTransaction.startActivityInTaskFragment(
+ mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
+ mTransaction.setAdjacentTaskFragments(mFragmentToken, mock(IBinder.class),
+ null /* options */);
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ // Successfully created a TaskFragment.
+ final TaskFragment taskFragment = mAtm.mWindowOrganizerController
+ .getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(activity.getTask(), taskFragment.getTask());
+ }
+
+ @Test
+ public void testApplyTransaction_createTaskFragment_failForDifferentUid()
+ throws RemoteException {
+ mController.registerOrganizer(mIOrganizer);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent);
+ final int uid = Binder.getCallingUid();
+ final IBinder fragmentToken = new Binder();
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token).build();
+ mOrganizer.applyTransaction(mTransaction);
+ mTransaction.createTaskFragment(params);
+
+ // Fail to create TaskFragment when the task uid is different from caller.
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid + 1;
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+ // Fail to create TaskFragment when the task uid is different from owner activity.
+ activity.info.applicationInfo.uid = uid + 1;
+ activity.getTask().effectiveUid = uid;
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ assertNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+
+ // Successfully created a TaskFragment for same uid.
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ assertNotNull(mAtm.mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
+ public void testApplyTransaction_enforceHierarchyChange_reparentChildren()
+ throws RemoteException {
+ mOrganizer.applyTransaction(mTransaction);
+ mController.registerOrganizer(mIOrganizer);
+ doReturn(true).when(mTaskFragment).isAttached();
+
+ // Throw exception if the transaction is trying to change a window that is not organized by
+ // the organizer.
+ mTransaction.reparentChildren(mFragmentWindowToken, null /* newParent */);
+
+ assertThrows(SecurityException.class, () -> {
+ try {
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+ } catch (RemoteException e) {
+ fail();
+ }
+ });
+
+ // Allow transaction to change a TaskFragment created by the organizer.
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */,
+ "Test:TaskFragmentOrganizer" /* processName */);
+ clearInvocations(mAtm.mRootWindowContainer);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ }
+
+ @Test
+ public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate()
+ throws RemoteException {
+ final ActivityRecord activity = createActivityRecord(mDefaultDisplay);
+ mOrganizer.applyTransaction(mTransaction);
+ mController.registerOrganizer(mIOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setFragmentToken(mFragmentToken)
+ .build();
+ mAtm.mWindowOrganizerController.mLaunchTaskFragments
+ .put(mFragmentToken, mTaskFragment);
+ mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.appToken);
+ clearInvocations(mAtm.mRootWindowContainer);
+
+ mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
+
+ verify(mAtm.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ }
+
+ @Test
+ public void testDeferPendingTaskFragmentEventsOfInvisibleTask() {
+ // Task - TaskFragment - Activity.
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .build();
+
+ // Mock the task to invisible
+ doReturn(false).when(task).shouldBeVisible(any());
+
+ // Sending events
+ mController.registerOrganizer(mIOrganizer);
+ taskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+
+ // Verifies that event was not sent
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ }
+
+ /**
+ * Tests that a task fragment info changed event is still sent if the task is invisible only
+ * when the info changed event is because of the last activity in a task finishing.
+ */
+ @Test
+ public void testLastPendingTaskFragmentInfoChangedEventOfInvisibleTaskSent() {
+ // Create a TaskFragment with an activity, all within a parent task
+ final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(mFragmentToken)
+ .setCreateParentTask()
+ .createActivityCount(1)
+ .build();
+ final Task parentTask = taskFragment.getTask();
+ final ActivityRecord activity = taskFragment.getTopNonFinishingActivity();
+ assertTrue(parentTask.shouldBeVisible(null));
+
+ // Dispatch pending info changed event from creating the activity
+ mController.registerOrganizer(mIOrganizer);
+ taskFragment.mTaskFragmentAppearedSent = true;
+ mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+
+ // Finish the activity and verify that the task is invisible
+ activity.finishing = true;
+ assertFalse(parentTask.shouldBeVisible(null));
+
+ // Verify the info changed callback still occurred despite the task being invisible
+ reset(mOrganizer);
+ mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
+ mController.dispatchPendingEvents();
+ verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
new file mode 100644
index 000000000000..730275cde40b
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.clearInvocations;
+
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOrganizer;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link TaskFragment}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:TaskFragmentTest
+ */
+@MediumTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskFragmentTest extends WindowTestsBase {
+
+ private TaskFragmentOrganizer mOrganizer;
+ private TaskFragment mTaskFragment;
+ private SurfaceControl mLeash;
+ @Mock
+ private SurfaceControl.Transaction mTransaction;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mOrganizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer iOrganizer =
+ ITaskFragmentOrganizer.Stub.asInterface(mOrganizer.getOrganizerToken().asBinder());
+ mAtm.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .registerOrganizer(iOrganizer);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setCreateParentTask()
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ mLeash = mTaskFragment.getSurfaceControl();
+ spyOn(mTaskFragment);
+ doReturn(mTransaction).when(mTaskFragment).getSyncTransaction();
+ doReturn(mTransaction).when(mTaskFragment).getPendingTransaction();
+ }
+
+ @Test
+ public void testOnConfigurationChanged_updateSurface() {
+ final Rect bounds = new Rect(100, 100, 1100, 1100);
+ mTaskFragment.setBounds(bounds);
+
+ verify(mTransaction).setPosition(mLeash, 100, 100);
+ verify(mTransaction).setWindowCrop(mLeash, 1000, 1000);
+ }
+
+ @Test
+ public void testStartChangeTransition_resetSurface() {
+ mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(500, 500, 1000, 1000);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ clearInvocations(mTransaction);
+ mTaskFragment.setBounds(endBounds);
+
+ // Surface reset when prepare transition.
+ verify(mTaskFragment).initializeChangeTransition(startBounds);
+ verify(mTransaction).setPosition(mLeash, 0, 0);
+ verify(mTransaction).setWindowCrop(mLeash, 0, 0);
+
+ clearInvocations(mTransaction);
+ mTaskFragment.mSurfaceFreezer.unfreeze(mTransaction);
+
+ // Update surface after animation.
+ verify(mTransaction).setPosition(mLeash, 500, 500);
+ verify(mTransaction).setWindowCrop(mLeash, 500, 500);
+ }
+
+ @Test
+ public void testNotOkToAnimate_doNotStartChangeTransition() {
+ mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(500, 500, 1000, 1000);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ displayPolicy.screenTurnedOff();
+
+ assertFalse(mTaskFragment.okToAnimate());
+
+ mTaskFragment.setBounds(endBounds);
+
+ verify(mTaskFragment, never()).initializeChangeTransition(any());
+ }
+
+ /**
+ * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an
+ * activity that has not yet been attached to a process because it is being initialized but
+ * belongs to the TaskFragmentOrganizer process is still reported in the TaskFragmentInfo.
+ */
+ @Test
+ public void testActivityStillReported_NotYetAssignedToProcess() {
+ mTaskFragment.addChild(new ActivityBuilder(mAtm).setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID)
+ .setProcessName(DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME).build());
+ final ActivityRecord activity = mTaskFragment.getTopMostActivity();
+ // Remove the process to simulate an activity that has not yet been attached to a process
+ activity.app = null;
+ final TaskFragmentInfo info = activity.getTaskFragment().getTaskFragmentInfo();
+ assertEquals(1, info.getRunningActivityCount());
+ assertEquals(1, info.getActivities().size());
+ assertEquals(false, info.isEmpty());
+ assertEquals(activity.token, info.getActivities().get(0));
+ }
+}
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 5e4c67ce9e5c..168c250a8c93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -1321,6 +1321,50 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ public void testDefaultFreeformSizeRespectsMinAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMinAspectRatio(5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder()
+ .setOptions(options).calculate());
+
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio,
+ aspectRatio >= 5f);
+ }
+
+ @Test
+ public void testDefaultFreeformSizeRespectsMaxAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMaxAspectRatio(1.5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder()
+ .setOptions(options).calculate());
+
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio,
+ aspectRatio <= 1.5f);
+ }
+
+ @Test
public void testCascadesToSourceSizeForFreeform() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1348,6 +1392,72 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ public void testCascadesToSourceSizeForFreeformRespectingMinAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMinAspectRatio(5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at least 5.0, but was " + aspectRatio,
+ aspectRatio >= 5f);
+ }
+
+ @Test
+ public void testCascadesToSourceSizeForFreeformRespectingMaxAspectRatio() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+ mActivity.info.setMaxAspectRatio(1.5f);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ final float aspectRatio =
+ (float) Math.max(mResult.mBounds.width(), mResult.mBounds.height())
+ / (float) Math.min(mResult.mBounds.width(), mResult.mBounds.height());
+ assertTrue("Bounds aspect ratio should be at most 1.5, but was " + aspectRatio,
+ aspectRatio <= 1.5f);
+ }
+
+ @Test
public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1716,7 +1826,7 @@ public class TaskLaunchParamsModifierTests extends WindowTestsBase {
final Task rootTask = display.getDefaultTaskDisplayArea()
.createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
- final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
// Just work around the unnecessary adjustments for bounds.
task.getWindowConfiguration().setBounds(bounds);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 97afc165624a..f573b70352af 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -144,7 +144,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
final int orientation = Configuration.ORIENTATION_PORTRAIT;
final float scaleFraction = 0.25f;
final Rect contentInsets = new Rect(1, 2, 3, 4);
- final Point taskSize = new Point(5, 6);
+ final Rect letterboxInsets = new Rect(5, 6, 7, 8);
+ final Point taskSize = new Point(9, 10);
try {
TaskSnapshot.Builder builder =
@@ -156,6 +157,7 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
builder.setColorSpace(sRGB);
builder.setOrientation(orientation);
builder.setContentInsets(contentInsets);
+ builder.setLetterboxInsets(letterboxInsets);
builder.setIsTranslucent(true);
builder.setSnapshot(buffer);
builder.setIsRealSnapshot(true);
@@ -176,6 +178,7 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
assertFalse(snapshot.isLowResolution());
assertEquals(orientation, snapshot.getOrientation());
assertEquals(contentInsets, snapshot.getContentInsets());
+ assertEquals(letterboxInsets, snapshot.getLetterboxInsets());
assertTrue(snapshot.isTranslucent());
assertSame(buffer, snapshot.getHardwareBuffer());
assertTrue(snapshot.isRealSnapshot());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index b5219fda1cc8..b8ac0be250a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -59,7 +59,8 @@ import java.util.function.Predicate;
*/
class TaskSnapshotPersisterTestBase extends WindowTestsBase {
- private static final Rect TEST_INSETS = new Rect(10, 20, 30, 40);
+ private static final Rect TEST_CONTENT_INSETS = new Rect(10, 20, 30, 40);
+ private static final Rect TEST_LETTERBOX_INSETS = new Rect();
static final File FILES_DIR = getInstrumentation().getTargetContext().getFilesDir();
static final long MOCK_SNAPSHOT_ID = 12345678;
@@ -208,7 +209,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
return new TaskSnapshot(MOCK_SNAPSHOT_ID, mTopActivityComponent,
HardwareBuffer.createFromGraphicBuffer(buffer),
ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
- mRotation, taskSize, TEST_INSETS,
+ mRotation, taskSize, TEST_CONTENT_INSETS, TEST_LETTERBOX_INSETS,
// When building a TaskSnapshot with the Builder class, isLowResolution
// is always false. Low-res snapshots are only created when loading from
// disk.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index 9372530f0e80..294aad237977 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -100,7 +100,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase {
System.currentTimeMillis(),
new ComponentName("", ""), buffer,
ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
- Surface.ROTATION_0, taskSize, contentInsets, false,
+ Surface.ROTATION_0, taskSize, contentInsets, new Rect() /* letterboxInsets*/, false,
true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
}
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 0ebff1d253ef..4ba3f63f5e63 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -44,6 +44,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.policy.WindowManagerPolicy.USER_ROTATION_FREE;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
import static com.google.common.truth.Truth.assertThat;
@@ -59,12 +60,14 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.never;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ComponentName;
@@ -126,8 +129,15 @@ public class TaskTests extends WindowTestsBase {
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- task.removeIfPossible();
- // Assert that the container was removed.
+ task.remove(false /* withTransition */, "testRemoveContainer");
+ // There is still an activity to be destroyed, so the task is not removed immediately.
+ assertNotNull(task.getParent());
+ assertTrue(rootTask.hasChild());
+ assertTrue(task.hasChild());
+ assertTrue(activity.finishing);
+
+ activity.destroyed("testRemoveContainer");
+ // Assert that the container was removed after the activity is destroyed.
assertNull(task.getParent());
assertEquals(0, task.getChildCount());
assertNull(activity.getParent());
@@ -420,7 +430,7 @@ public class TaskTests extends WindowTestsBase {
TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
Task rootTask = taskDisplayArea.createRootTask(WINDOWING_MODE_FREEFORM,
ACTIVITY_TYPE_STANDARD, true /* onTop */);
- Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
final Configuration parentConfig = rootTask.getConfiguration();
parentConfig.windowConfiguration.setBounds(parentBounds);
parentConfig.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
@@ -750,7 +760,7 @@ public class TaskTests extends WindowTestsBase {
DisplayInfo displayInfo = new DisplayInfo();
mAtm.mContext.getDisplay().getDisplayInfo(displayInfo);
final int displayHeight = displayInfo.logicalHeight;
- final Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ final Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
final Configuration inOutConfig = new Configuration();
final Configuration parentConfig = new Configuration();
final int longSide = 1200;
@@ -895,10 +905,10 @@ public class TaskTests extends WindowTestsBase {
*/
@Test
public void testFindRootIndex_effectiveRoot_finishingAndRelinquishing() {
- final Task task = getTestTask();
+ final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity0.getTask();
// Add extra two activities. Mark the one on the bottom with "relinquishTaskIdentity" and
// one above as finishing.
- final ActivityRecord activity0 = task.getBottomMostActivity();
activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
activity1.finishing = true;
@@ -930,9 +940,9 @@ public class TaskTests extends WindowTestsBase {
*/
@Test
public void testFindRootIndex_effectiveRoot_relinquishingMultipleActivities() {
- final Task task = getTestTask();
+ final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity0.getTask();
// Set relinquishTaskIdentity for all activities in the task
- final ActivityRecord activity0 = task.getBottomMostActivity();
activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
@@ -1082,9 +1092,9 @@ public class TaskTests extends WindowTestsBase {
*/
@Test
public void testGetTaskForActivity_onlyRoot_relinquishTaskIdentity() {
- final Task task = getTestTask();
+ final ActivityRecord activity0 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task task = activity0.getTask();
// Make the current root activity relinquish task identity
- final ActivityRecord activity0 = task.getBottomMostActivity();
activity0.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
// Add an extra activity on top - this will be the new root
final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
@@ -1180,6 +1190,46 @@ public class TaskTests extends WindowTestsBase {
verify(task).setIntent(eq(activity0));
}
+ /**
+ * Test {@link Task#updateEffectiveIntent()} when activity with relinquishTaskIdentity but
+ * another with different uid. This should make the task use the root activity when updating the
+ * intent.
+ */
+ @Test
+ public void testUpdateEffectiveIntent_relinquishingWithDifferentUid() {
+ final ActivityRecord activity0 = new ActivityBuilder(mAtm)
+ .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build();
+ final Task task = activity0.getTask();
+
+ // Add an extra activity on top
+ new ActivityBuilder(mAtm).setUid(11).setTask(task).build();
+
+ spyOn(task);
+ task.updateEffectiveIntent();
+ verify(task).setIntent(eq(activity0));
+ }
+
+ /**
+ * Test {@link Task#updateEffectiveIntent()} with activities set as relinquishTaskIdentity.
+ * This should make the task use the topmost activity when updating the intent.
+ */
+ @Test
+ public void testUpdateEffectiveIntent_relinquishingMultipleActivities() {
+ final ActivityRecord activity0 = new ActivityBuilder(mAtm)
+ .setActivityFlags(FLAG_RELINQUISH_TASK_IDENTITY).setCreateTask(true).build();
+ final Task task = activity0.getTask();
+ // Add an extra activity on top
+ final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task).build();
+ activity1.info.flags |= FLAG_RELINQUISH_TASK_IDENTITY;
+
+ // Add an extra activity on top
+ final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task).build();
+
+ spyOn(task);
+ task.updateEffectiveIntent();
+ verify(task).setIntent(eq(activity2));
+ }
+
@Test
public void testSaveLaunchingStateWhenConfigurationChanged() {
LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
@@ -1257,7 +1307,8 @@ public class TaskTests extends WindowTestsBase {
LaunchParamsPersister persister = mAtm.mTaskSupervisor.mLaunchParamsPersister;
spyOn(persister);
- final Task task = getTestTask();
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true)
+ .setCreateParentTask(true).build().getRootTask();
task.setHasBeenVisible(false);
task.getDisplayContent().setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -1356,6 +1407,48 @@ public class TaskTests extends WindowTestsBase {
assertNotNull(activity.getTask().getDimmer());
}
+ @Test
+ public void testMoveToFront_moveAdjacentTask() {
+ final Task task1 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final Task task2 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ spyOn(task2);
+
+ task1.setAdjacentTaskFragment(task2, false /* moveTogether */);
+ task1.moveToFront("" /* reason */);
+ verify(task2, never()).moveToFrontInner(anyString(), isNull());
+
+ // Reset adjacent tasks to move together.
+ task1.setAdjacentTaskFragment(null, false /* moveTogether */);
+ task1.setAdjacentTaskFragment(task2, true /* moveTogether */);
+ task1.moveToFront("" /* reason */);
+ verify(task2).moveToFrontInner(anyString(), isNull());
+ }
+
+ @Test
+ public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tfBehind = createTaskFragmentWithParentTask(
+ task, false /* createEmbeddedTask */);
+ final TaskFragment tfFront = createTaskFragmentWithParentTask(
+ task, false /* createEmbeddedTask */);
+ spyOn(tfFront);
+ doReturn(true).when(tfFront).isTranslucent(any());
+
+ // TaskFragment behind another translucent TaskFragment should not be resumed.
+ assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+ tfBehind.getVisibility(null /* starting */));
+ assertTrue(tfBehind.isFocusable());
+ assertFalse(tfBehind.canBeResumed(null /* starting */));
+
+ spyOn(tfBehind);
+ task.resumeTopActivityUncheckedLocked(null /* prev */, ActivityOptions.makeBasic(),
+ false /* deferPause */);
+
+ verify(tfBehind, never()).resumeTopActivity(any(), any(), anyBoolean());
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
@@ -1367,7 +1460,7 @@ public class TaskTests extends WindowTestsBase {
TaskDisplayArea taskDisplayArea = mAtm.mRootWindowContainer.getDefaultTaskDisplayArea();
Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD,
true /* onTop */);
- Task task = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
+ Task task = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build();
final Configuration parentConfig = rootTask.getConfiguration();
parentConfig.windowConfiguration.setAppBounds(parentBounds);
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 ce2d74859931..e0f69d4f2eaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -19,7 +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 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;
@@ -77,6 +79,7 @@ class TestDisplayContent extends DisplayContent {
private int mPosition = POSITION_BOTTOM;
protected final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
+ private int mStatusBarHeight = 0;
Builder(ActivityTaskManagerService service, int width, int height) {
mService = service;
@@ -125,6 +128,10 @@ class TestDisplayContent extends DisplayContent {
Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null);
return this;
}
+ Builder setStatusBarHeight(int height) {
+ mStatusBarHeight = height;
+ return this;
+ }
Builder setCanRotate(boolean canRotate) {
mCanRotate = canRotate;
return this;
@@ -158,6 +165,19 @@ class TestDisplayContent extends DisplayContent {
doReturn(false).when(displayPolicy).hasStatusBar();
doReturn(false).when(newDisplay).supportsSystemDecorations();
}
+ // Update the display policy to make the screen fully turned on so animation is allowed
+ displayPolicy.screenTurnedOn(null /* screenOnListener */);
+ displayPolicy.finishKeyguardDrawn();
+ displayPolicy.finishWindowsDrawn();
+ displayPolicy.finishScreenTurningOn();
+ if (mStatusBarHeight > 0) {
+ doReturn(true).when(displayPolicy).hasStatusBar();
+ doAnswer(invocation -> {
+ Rect inOutInsets = (Rect) invocation.getArgument(0);
+ inOutInsets.top = mStatusBarHeight;
+ return null;
+ }).when(displayPolicy).convertNonDecorInsetsToStableInsets(any(), anyInt());
+ }
Configuration c = new Configuration();
newDisplay.computeScreenConfiguration(c);
c.windowConfiguration.setWindowingMode(mWindowingMode);
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 acadb74d333f..9001578cf37a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -128,10 +128,6 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public void setKeyguardCandidateLw(WindowState win) {
- }
-
- @Override
public Animation createHiddenByKeyguardExit(boolean onWallpaper,
boolean goingToNotificationShade, boolean subtleAnimation) {
return null;
@@ -368,7 +364,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public int applyKeyguardOcclusionChange() {
+ public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) {
return 0;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 2dfb3a1a84bc..d9a166a62673 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+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_IS_WALLPAPER;
@@ -33,14 +33,19 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
+import android.window.ITransitionPlayer;
import android.window.TransitionInfo;
import androidx.test.filters.SmallTest;
@@ -48,9 +53,12 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* Build/Install/Run:
- * atest WmTests:TransitionRecordTests
+ * atest WmTests:TransitionTests
*/
@SmallTest
@Presubmit
@@ -59,13 +67,13 @@ public class TransitionTests extends WindowTestsBase {
private Transition createTestTransition(int transitType) {
TransitionController controller = mock(TransitionController.class);
- BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
- return new Transition(transitType, 0 /* flags */, controller, sync);
+ final BLASTSyncEngine sync = createTestBLASTSyncEngine();
+ return new Transition(transitType, 0 /* flags */, 0 /* timeoutMs */, controller, sync);
}
@Test
public void testCreateInfo_NewTask() {
- final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -83,7 +91,7 @@ public class TransitionTests extends WindowTestsBase {
closing.mVisibleRequested = false;
opening.mVisibleRequested = true;
- int transit = TRANSIT_OLD_TASK_OPEN;
+ final int transit = transition.mType;
int flags = 0;
// Check basic both tasks participating
@@ -122,7 +130,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_NestedTasks() {
- final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -147,7 +155,7 @@ public class TransitionTests extends WindowTestsBase {
opening.mVisibleRequested = true;
opening2.mVisibleRequested = true;
- int transit = TRANSIT_OLD_TASK_OPEN;
+ final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
@@ -172,7 +180,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_DisplayArea() {
- final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
ArraySet<WindowContainer> participants = transition.mParticipants;
final Task showTask = createTask(mDisplayContent);
@@ -194,7 +202,7 @@ public class TransitionTests extends WindowTestsBase {
showing.mVisibleRequested = true;
showing2.mVisibleRequested = true;
- int transit = TRANSIT_OLD_TASK_OPEN;
+ final int transit = transition.mType;
int flags = 0;
// Check promotion to DisplayArea
@@ -223,7 +231,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_existenceChange() {
- final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
final Task openTask = createTask(mDisplayContent);
final ActivityRecord opening = createActivityRecord(openTask);
@@ -253,7 +261,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_ordering() {
- final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
// pick some number with a high enough chance of being out-of-order when added to set.
final int taskCount = 6;
@@ -289,7 +297,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testCreateInfo_wallpaper() {
- final Transition transition = createTestTransition(TRANSIT_OLD_TASK_OPEN);
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
// pick some number with a high enough chance of being out-of-order when added to set.
final int taskCount = 4;
final int showWallpaperTask = 2;
@@ -339,6 +347,44 @@ public class TransitionTests extends WindowTestsBase {
}
@Test
+ public void testTargets_noIntermediatesToWallpaper() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+
+ final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+ mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+ // Make DA organized so we can check that they don't get included.
+ WindowContainer parent = wallpaperWindowToken.getParent();
+ while (parent != null && parent != mDisplayContent) {
+ if (parent.asDisplayArea() != null) {
+ parent.asDisplayArea().setOrganizer(
+ mock(android.window.IDisplayAreaOrganizer.class), true /* skipAppear */);
+ }
+ parent = parent.getParent();
+ }
+ final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
+ "wallpaperWindow");
+ wallpaperWindowToken.setVisibleRequested(false);
+ transition.collect(wallpaperWindowToken);
+ wallpaperWindowToken.setVisibleRequested(true);
+ wallpaperWindow.mHasSurface = true;
+ doReturn(true).when(mDisplayContent).isAttached();
+ transition.collect(mDisplayContent);
+ mDisplayContent.getWindowConfiguration().setRotation(
+ (mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
+
+ ArraySet<WindowContainer> targets = Transition.calculateTargets(
+ transition.mParticipants, transition.mChanges);
+ TransitionInfo info = Transition.calculateTransitionInfo(
+ 0, 0, targets, transition.mChanges);
+ // The wallpaper is not organized, so it won't have a token; however, it will be marked
+ // as IS_WALLPAPER
+ assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags());
+ // Make sure no intermediate display areas were pulled in between wallpaper and display.
+ assertEquals(mDisplayContent.mRemoteToken.toWindowContainerToken(),
+ info.getChanges().get(0).getParent());
+ }
+
+ @Test
public void testIndependent() {
final Transition transition = createTestTransition(TRANSIT_OPEN);
ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
@@ -376,7 +422,7 @@ public class TransitionTests extends WindowTestsBase {
openInOpen.mVisibleRequested = true;
openInChange.mVisibleRequested = true;
- int transit = TRANSIT_OLD_TASK_OPEN;
+ final int transit = transition.mType;
int flags = 0;
// Check full promotion from leaf
@@ -406,6 +452,153 @@ public class TransitionTests extends WindowTestsBase {
info.getChange(openInChangeTask.mRemoteToken.toWindowContainerToken()), info));
}
+ @Test
+ public void testTimeout() {
+ final TransitionController controller = new TransitionController(mAtm,
+ mock(TaskSnapshotController.class));
+ final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
+ final CountDownLatch latch = new CountDownLatch(1);
+ // When the timeout is reached, it will finish the sync-group and notify transaction ready.
+ new Transition(TRANSIT_OPEN, 0 /* flags */, 10 /* timeoutMs */, controller, sync) {
+ @Override
+ public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
+ latch.countDown();
+ }
+ };
+ assertTrue(awaitInWmLock(() -> latch.await(3, TimeUnit.SECONDS)));
+ }
+
+ @Test
+ public void testIntermediateVisibility() {
+ final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
+ final TransitionController controller = new TransitionController(mAtm, snapshotController);
+ final ITransitionPlayer player = new ITransitionPlayer.Default();
+ controller.registerTransitionPlayer(player, null /* appThread */);
+ ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
+ final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
+
+ // Start out with task2 visible and set up a transition that closes task2 and opens task1
+ final Task task1 = createTask(mDisplayContent);
+ task1.mTaskOrganizer = mockOrg;
+ final ActivityRecord activity1 = createActivityRecord(task1);
+ activity1.mVisibleRequested = false;
+ activity1.setVisible(false);
+ final Task task2 = createTask(mDisplayContent);
+ task2.mTaskOrganizer = mockOrg;
+ final ActivityRecord activity2 = createActivityRecord(task1);
+ activity2.mVisibleRequested = true;
+ activity2.setVisible(true);
+
+ openTransition.collectExistenceChange(task1);
+ openTransition.collectExistenceChange(activity1);
+ openTransition.collectExistenceChange(task2);
+ openTransition.collectExistenceChange(activity2);
+
+ activity1.mVisibleRequested = true;
+ activity1.setVisible(true);
+ activity2.mVisibleRequested = false;
+
+ // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
+ // We didn't call abort on the transition itself, so it will still run onTransactionReady
+ // normally.
+ mWm.mSyncEngine.abort(openTransition.getSyncId());
+
+ // Before finishing openTransition, we are now going to simulate closing task1 to return
+ // back to (open) task2.
+ final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
+
+ closeTransition.collectExistenceChange(task1);
+ closeTransition.collectExistenceChange(activity1);
+ closeTransition.collectExistenceChange(task2);
+ closeTransition.collectExistenceChange(activity2);
+
+ activity1.mVisibleRequested = false;
+ activity2.mVisibleRequested = true;
+
+ openTransition.finishTransition();
+
+ // We finished the openTransition. Even though activity1 is visibleRequested=false, since
+ // the closeTransition animation hasn't played yet, make sure that we didn't commit
+ // visible=false on activity1 since it needs to remain visible for the animation.
+ assertTrue(activity1.isVisible());
+ assertTrue(activity2.isVisible());
+
+ // Using abort to force-finish the sync (since we obviously can't wait for drawing).
+ // We didn't call abort on the actual transition, so it will still run onTransactionReady
+ // normally.
+ mWm.mSyncEngine.abort(closeTransition.getSyncId());
+
+ closeTransition.finishTransition();
+
+ assertFalse(activity1.isVisible());
+ assertTrue(activity2.isVisible());
+ }
+
+ @Test
+ public void testTransientLaunch() {
+ final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
+ final TransitionController controller = new TransitionController(mAtm, snapshotController);
+ final ITransitionPlayer player = new ITransitionPlayer.Default();
+ controller.registerTransitionPlayer(player, null /* appThread */);
+ ITaskOrganizer mockOrg = mock(ITaskOrganizer.class);
+ final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
+
+ // Start out with task2 visible and set up a transition that closes task2 and opens task1
+ final Task task1 = createTask(mDisplayContent);
+ task1.mTaskOrganizer = mockOrg;
+ final ActivityRecord activity1 = createActivityRecord(task1);
+ activity1.mVisibleRequested = false;
+ activity1.setVisible(false);
+ final Task task2 = createTask(mDisplayContent);
+ task2.mTaskOrganizer = mockOrg;
+ final ActivityRecord activity2 = createActivityRecord(task2);
+ activity2.mVisibleRequested = true;
+ activity2.setVisible(true);
+
+ openTransition.collectExistenceChange(task1);
+ openTransition.collectExistenceChange(activity1);
+ openTransition.collectExistenceChange(task2);
+ openTransition.collectExistenceChange(activity2);
+
+ activity1.mVisibleRequested = true;
+ activity1.setVisible(true);
+ activity2.mVisibleRequested = false;
+
+ // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
+ // We didn't call abort on the transition itself, so it will still run onTransactionReady
+ // normally.
+ mWm.mSyncEngine.abort(openTransition.getSyncId());
+
+ verify(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false));
+
+ openTransition.finishTransition();
+
+ // We are now going to simulate closing task1 to return back to (open) task2.
+ final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
+
+ closeTransition.collectExistenceChange(task1);
+ closeTransition.collectExistenceChange(activity1);
+ closeTransition.collectExistenceChange(task2);
+ closeTransition.collectExistenceChange(activity2);
+ closeTransition.setTransientLaunch(activity2);
+
+ activity1.mVisibleRequested = false;
+ activity2.mVisibleRequested = true;
+
+ // Using abort to force-finish the sync (since we obviously can't wait for drawing).
+ // We didn't call abort on the actual transition, so it will still run onTransactionReady
+ // normally.
+ mWm.mSyncEngine.abort(closeTransition.getSyncId());
+
+ // Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be
+ // called until finish).
+ verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false));
+
+ closeTransition.finishTransition();
+
+ verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
+ }
+
/** Fill the change map with all the parents of top. Change maps are usually fully populated */
private static void fillChangeMap(ArrayMap<WindowContainer, Transition.ChangeInfo> changes,
WindowContainer top) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 3f0c13c83816..f366f57bae08 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -316,7 +316,8 @@ public class WallpaperControllerTests extends WindowTestsBase {
final IBinder mockBinder = mock(IBinder.class);
final ITransitionPlayer mockPlayer = mock(ITransitionPlayer.class);
doReturn(mockBinder).when(mockPlayer).asBinder();
- mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer);
+ mWm.mAtmService.getTransitionController().registerTransitionPlayer(mockPlayer,
+ null /* appThread */);
Transition transit =
mWm.mAtmService.getTransitionController().createTransition(TRANSIT_OPEN);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
index e970c2a2f2ed..e2f1334c7f8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
import static org.mockito.ArgumentMatchers.any;
@@ -89,43 +88,6 @@ public class WindowAnimationSpecTest {
}
@Test
- public void testApply_clipBeforeNoAnimationBounds() {
- // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 0, 0)
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
- argThat(rect -> rect.equals(mStackBounds)));
- }
-
- @Test
- public void testApply_clipBeforeNoStackBounds() {
- // Stack bounds is (0, 0, 0, 0) animation clip is (0, 0, 20, 20)
- Rect windowCrop = new Rect(0, 0, 20, 20);
- Animation a = createClipRectAnimation(windowCrop, windowCrop);
- a.initialize(0, 0, 0, 0);
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
- null, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty));
- }
-
- @Test
- public void testApply_setCornerRadius() {
- final float windowCornerRadius = 30f;
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, windowCornerRadius);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction, never()).setCornerRadius(eq(mSurfaceControl), eq(windowCornerRadius));
- when(mAnimation.hasRoundedCorners()).thenReturn(true);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setCornerRadius(eq(mSurfaceControl), eq(windowCornerRadius));
- }
-
- @Test
public void testApply_setCornerRadius_noClip() {
final float windowCornerRadius = 30f;
WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(mAnimation, null,
@@ -136,32 +98,6 @@ public class WindowAnimationSpecTest {
verify(mTransaction, never()).setCornerRadius(any(), anyFloat());
}
- @Test
- public void testApply_clipBeforeSmallerAnimationClip() {
- // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 5, 5)
- Rect windowCrop = new Rect(0, 0, 5, 5);
- Animation a = createClipRectAnimation(windowCrop, windowCrop);
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
- argThat(rect -> rect.equals(windowCrop)));
- }
-
- @Test
- public void testApply_clipBeforeSmallerStackClip() {
- // Stack bounds is (0, 0, 10, 10) animation clip is (0, 0, 20, 20)
- Rect windowCrop = new Rect(0, 0, 20, 20);
- Animation a = createClipRectAnimation(windowCrop, windowCrop);
- WindowAnimationSpec windowAnimationSpec = new WindowAnimationSpec(a, null,
- mStackBounds, false /* canSkipFirstFrame */, ROOT_TASK_CLIP_BEFORE_ANIM,
- true /* isAppAnimation */, 0 /* windowCornerRadius */);
- windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0);
- verify(mTransaction).setWindowCrop(eq(mSurfaceControl),
- argThat(rect -> rect.equals(mStackBounds)));
- }
-
private Animation createClipRectAnimation(Rect fromClip, Rect toClip) {
Animation a = new ClipRectAnimation(fromClip, toClip);
a.initialize(0, 0, 0, 0);
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 00f3d8b874f7..6ae3f9447ee2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
@@ -52,6 +53,7 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -1071,6 +1073,163 @@ public class WindowContainerTests extends WindowTestsBase {
verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 1 /* layer */);
}
+ @Test
+ public void testAssignAnimationLayer() {
+ final WindowContainer container = new WindowContainer(mWm);
+ container.mSurfaceControl = mock(SurfaceControl.class);
+ final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
+ final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
+ final SurfaceControl relativeParent = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ spyOn(container);
+ spyOn(surfaceAnimator);
+ spyOn(surfaceFreezer);
+
+ container.setLayer(t, 1);
+ container.setRelativeLayer(t, relativeParent, 2);
+
+ // Set through surfaceAnimator if surfaceFreezer doesn't have leash.
+ verify(surfaceAnimator).setLayer(t, 1);
+ verify(surfaceAnimator).setRelativeLayer(t, relativeParent, 2);
+ verify(surfaceFreezer, never()).setLayer(any(), anyInt());
+ verify(surfaceFreezer, never()).setRelativeLayer(any(), any(), anyInt());
+
+ clearInvocations(surfaceAnimator);
+ clearInvocations(surfaceFreezer);
+ doReturn(true).when(surfaceFreezer).hasLeash();
+
+ container.setLayer(t, 1);
+ container.setRelativeLayer(t, relativeParent, 2);
+
+ // Set through surfaceFreezer if surfaceFreezer has leash.
+ verify(surfaceFreezer).setLayer(t, 1);
+ verify(surfaceFreezer).setRelativeLayer(t, relativeParent, 2);
+ verify(surfaceAnimator, never()).setLayer(any(), anyInt());
+ verify(surfaceAnimator, never()).setRelativeLayer(any(), any(), anyInt());
+ }
+
+ @Test
+ public void testStartChangeTransitionWhenPreviousIsNotFinished() {
+ final WindowContainer container = createTaskFragmentWithParentTask(
+ createTask(mDisplayContent), false);
+ container.mSurfaceControl = mock(SurfaceControl.class);
+ final SurfaceAnimator surfaceAnimator = container.mSurfaceAnimator;
+ final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ spyOn(container);
+ spyOn(surfaceAnimator);
+ mockSurfaceFreezerSnapshot(surfaceFreezer);
+ doReturn(t).when(container).getPendingTransaction();
+ doReturn(t).when(container).getSyncTransaction();
+
+ // Leash and snapshot created for change transition.
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+
+ assertNotNull(surfaceFreezer.mLeash);
+ assertNotNull(surfaceFreezer.mSnapshot);
+ assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
+
+ // Start animation: surfaceAnimator take over the leash and snapshot from surfaceFreezer.
+ container.applyAnimationUnchecked(null /* lp */, true /* enter */,
+ TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
+ null /* sources */);
+
+ assertNull(surfaceFreezer.mLeash);
+ assertNull(surfaceFreezer.mSnapshot);
+ assertNotNull(surfaceAnimator.mLeash);
+ assertNotNull(surfaceAnimator.mSnapshot);
+ final SurfaceControl prevLeash = surfaceAnimator.mLeash;
+ final SurfaceFreezer.Snapshot prevSnapshot = surfaceAnimator.mSnapshot;
+
+ // Prepare another change transition.
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+
+ assertNotNull(surfaceFreezer.mLeash);
+ assertNotNull(surfaceFreezer.mSnapshot);
+ assertEquals(surfaceFreezer.mLeash, container.getAnimationLeash());
+ assertNotEquals(prevLeash, container.getAnimationLeash());
+
+ // Start another animation before the previous one is finished, it should reset the previous
+ // one, but not change the current one.
+ container.applyAnimationUnchecked(null /* lp */, true /* enter */,
+ TRANSIT_OLD_TASK_FRAGMENT_CHANGE, false /* isVoiceInteraction */,
+ null /* sources */);
+
+ verify(container, never()).onAnimationLeashLost(any());
+ verify(surfaceFreezer, never()).unfreeze(any());
+ assertNotNull(surfaceAnimator.mLeash);
+ assertNotNull(surfaceAnimator.mSnapshot);
+ assertEquals(surfaceAnimator.mLeash, container.getAnimationLeash());
+ assertNotEquals(prevLeash, surfaceAnimator.mLeash);
+ assertNotEquals(prevSnapshot, surfaceAnimator.mSnapshot);
+
+ // Clean up after animation finished.
+ surfaceAnimator.mInnerAnimationFinishedCallback.onAnimationFinished(
+ ANIMATION_TYPE_APP_TRANSITION, surfaceAnimator.getAnimation());
+
+ verify(container).onAnimationLeashLost(any());
+ assertNull(surfaceAnimator.mLeash);
+ assertNull(surfaceAnimator.mSnapshot);
+ }
+
+ @Test
+ public void testUnfreezeWindow_removeWindowFromChanging() {
+ final WindowContainer container = createTaskFragmentWithParentTask(
+ createTask(mDisplayContent), false);
+ mockSurfaceFreezerSnapshot(container.mSurfaceFreezer);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+
+ assertTrue(mDisplayContent.mChangingContainers.contains(container));
+
+ container.mSurfaceFreezer.unfreeze(t);
+
+ assertFalse(mDisplayContent.mChangingContainers.contains(container));
+ }
+
+ @Test
+ public void testFailToTaskSnapshot_unfreezeWindow() {
+ final WindowContainer container = createTaskFragmentWithParentTask(
+ createTask(mDisplayContent), false);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ spyOn(container.mSurfaceFreezer);
+
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+
+ verify(container.mSurfaceFreezer).freeze(any(), any(), any(), any());
+ verify(container.mSurfaceFreezer).unfreeze(any());
+ assertTrue(mDisplayContent.mChangingContainers.isEmpty());
+ }
+
+ @Test
+ public void testRemoveUnstartedFreezeSurfaceWhenFreezeAgain() {
+ final WindowContainer container = createTaskFragmentWithParentTask(
+ createTask(mDisplayContent), false);
+ container.mSurfaceControl = mock(SurfaceControl.class);
+ final SurfaceFreezer surfaceFreezer = container.mSurfaceFreezer;
+ mockSurfaceFreezerSnapshot(surfaceFreezer);
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ spyOn(container);
+ doReturn(t).when(container).getPendingTransaction();
+ doReturn(t).when(container).getSyncTransaction();
+
+ // Leash and snapshot created for change transition.
+ container.initializeChangeTransition(new Rect(0, 0, 1000, 2000));
+
+ assertNotNull(surfaceFreezer.mLeash);
+ assertNotNull(surfaceFreezer.mSnapshot);
+
+ final SurfaceControl prevLeash = surfaceFreezer.mLeash;
+ final SurfaceFreezer.Snapshot prevSnapshot = surfaceFreezer.mSnapshot;
+ spyOn(prevSnapshot);
+
+ container.initializeChangeTransition(new Rect(0, 0, 1500, 2500));
+
+ verify(t).remove(prevLeash);
+ verify(prevSnapshot).destroy(t);
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
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 e5eba57f223d..646647fcc4ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -17,22 +17,35 @@
package com.android.server.wm;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.STATE_OFF;
+import static android.view.Display.STATE_ON;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.window.WindowProvider.KEY_IS_WINDOW_PROVIDER_SERVICE;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
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.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import android.app.IWindowToken;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
@@ -55,12 +68,15 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
private static final int ANOTHER_UID = 1000;
private final IBinder mClientToken = new Binder();
- private WindowContainer mContainer;
+ private WindowContainer<?> mContainer;
@Before
public void setUp() {
mController = new WindowContextListenerController();
mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
+ // Make display on to verify configuration propagation.
+ mDefaultDisplay.getDisplayInfo().state = STATE_ON;
+ mDisplayContent.getDisplayInfo().state = STATE_ON;
}
@Test
@@ -76,7 +92,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
assertEquals(2, mController.mListeners.size());
- final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
+ final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
mDefaultDisplay);
mController.registerWindowContainerListener(mClientToken, container, -1,
TYPE_APPLICATION_OVERLAY, null /* options */);
@@ -89,6 +105,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
assertEquals(container, listener.getWindowContainer());
}
+ @UseTestDisplay
@Test
public void testRegisterWindowContextListenerClientConfigPropagation() {
final TestWindowTokenClient clientToken = new TestWindowTokenClient();
@@ -107,7 +124,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
assertEquals(mDisplayContent.mDisplayId, clientToken.mDisplayId);
// Update the WindowContainer.
- final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
+ final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY,
mDefaultDisplay);
final Configuration config2 = container.getConfiguration();
final Rect bounds2 = new Rect(0, 0, 20, 20);
@@ -174,7 +191,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
.setDisplayContent(mDefaultDisplay)
.setFromClientToken(true)
.build();
- final DisplayArea da = windowContextCreatedToken.getDisplayArea();
+ final DisplayArea<?> da = windowContextCreatedToken.getDisplayArea();
mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken,
TEST_UID, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
@@ -192,11 +209,12 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
// Let the Display to be created with the DualDisplay policy.
final DisplayAreaPolicy.Provider policyProvider =
new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider();
- Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
+ doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider();
// Create a DisplayContent with dual RootDisplayArea
DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent =
new DualDisplayAreaGroupPolicyTest.DualDisplayContent
.Builder(mAtm, 1000, 1000).build();
+ dualDisplayContent.getDisplayInfo().state = STATE_ON;
final DisplayArea.Tokens imeContainer = dualDisplayContent.getImeContainer();
// Put the ImeContainer to the first sub-RootDisplayArea
dualDisplayContent.mFirstRoot.placeImeContainer(imeContainer);
@@ -222,7 +240,62 @@ public class WindowContextListenerControllerTests extends WindowTestsBase {
assertThat(mController.getContainer(mClientToken)).isEqualTo(imeContainer);
}
- private class TestWindowTokenClient extends IWindowToken.Stub {
+ @Test
+ public void testConfigUpdateForSuspendedWindowContext() {
+ final TestWindowTokenClient mockToken = new TestWindowTokenClient();
+ spyOn(mockToken);
+
+ mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF;
+
+ final Configuration config1 = mContainer.getConfiguration();
+ final Rect bounds1 = new Rect(0, 0, 10, 10);
+ config1.windowConfiguration.setBounds(bounds1);
+ config1.densityDpi = 100;
+ mContainer.onRequestedOverrideConfigurationChanged(config1);
+
+ mController.registerWindowContainerListener(mockToken, mContainer, -1,
+ TYPE_APPLICATION_OVERLAY, null /* options */);
+
+ verify(mockToken, never()).onConfigurationChanged(any(), anyInt());
+
+ // Turn on the display and verify if the client receive the callback
+ Display display = mContainer.getDisplayContent().getDisplay();
+ spyOn(display);
+ Mockito.doAnswer(invocation -> {
+ final DisplayInfo info = mContainer.getDisplayContent().getDisplayInfo();
+ info.state = STATE_ON;
+ ((DisplayInfo) invocation.getArgument(0)).copyFrom(info);
+ return null;
+ }).when(display).getDisplayInfo(any(DisplayInfo.class));
+
+ mContainer.getDisplayContent().onDisplayChanged();
+
+ assertThat(mockToken.mConfiguration).isEqualTo(config1);
+ assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId());
+ }
+
+ @Test
+ public void testReportConfigUpdateForSuspendedWindowProviderService() {
+ final TestWindowTokenClient clientToken = new TestWindowTokenClient();
+ final Bundle options = new Bundle();
+ options.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true);
+
+ mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF;
+
+ final Configuration config1 = mContainer.getConfiguration();
+ final Rect bounds1 = new Rect(0, 0, 10, 10);
+ config1.windowConfiguration.setBounds(bounds1);
+ config1.densityDpi = 100;
+ mContainer.onRequestedOverrideConfigurationChanged(config1);
+
+ mController.registerWindowContainerListener(clientToken, mContainer, -1,
+ TYPE_APPLICATION_OVERLAY, options);
+
+ assertThat(clientToken.mConfiguration).isEqualTo(config1);
+ assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId);
+ }
+
+ private static class TestWindowTokenClient extends IWindowToken.Stub {
private Configuration mConfiguration;
private int mDisplayId;
private boolean mRemoved;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index a1f89ec75784..316309c8440e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -35,6 +35,7 @@ import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.WindowManager;
import androidx.test.filters.FlakyTest;
@@ -57,12 +58,14 @@ import org.mockito.Mockito;
public class WindowFrameTests extends WindowTestsBase {
private DisplayContent mTestDisplayContent;
+ private DisplayFrames mTestDisplayFrames;
@Before
public void setUp() throws Exception {
DisplayInfo testDisplayInfo = new DisplayInfo(mDisplayInfo);
testDisplayInfo.displayCutout = null;
mTestDisplayContent = createNewDisplay(testDisplayInfo);
+ mTestDisplayFrames = mTestDisplayContent.mDisplayFrames;
}
// Do not use this function directly in the tests below. Instead, use more explicit function
@@ -99,7 +102,7 @@ public class WindowFrameTests extends WindowTestsBase {
// Here the window has FILL_PARENT, FILL_PARENT
// so we expect it to fill the entire available frame.
w.getWindowFrames().setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 0, 0, 1000, 1000);
assertRelFrame(w, 0, 0, 1000, 1000);
@@ -108,14 +111,14 @@ public class WindowFrameTests extends WindowTestsBase {
// and we use mRequestedWidth/mRequestedHeight
w.mAttrs.width = 300;
w.mAttrs.height = 300;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
// Explicit width and height without requested width/height
// gets us nothing.
assertFrame(w, 0, 0, 0, 0);
w.mRequestedWidth = 300;
w.mRequestedHeight = 300;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
// With requestedWidth/Height we can freely choose our size within the
// parent bounds.
assertFrame(w, 0, 0, 300, 300);
@@ -128,14 +131,14 @@ public class WindowFrameTests extends WindowTestsBase {
w.mRequestedWidth = -1;
w.mAttrs.width = 100;
w.mAttrs.height = 100;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 0, 0, 100, 100);
w.mAttrs.flags = 0;
// But sizes too large will be clipped to the containing frame
w.mRequestedWidth = 1200;
w.mRequestedHeight = 1200;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 0, 0, 1000, 1000);
// Before they are clipped though windows will be shifted
@@ -143,7 +146,7 @@ public class WindowFrameTests extends WindowTestsBase {
w.mAttrs.y = 300;
w.mRequestedWidth = 1000;
w.mRequestedHeight = 1000;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 0, 0, 1000, 1000);
// If there is room to move around in the parent frame the window will be shifted according
@@ -153,18 +156,18 @@ public class WindowFrameTests extends WindowTestsBase {
w.mRequestedWidth = 300;
w.mRequestedHeight = 300;
w.mAttrs.gravity = Gravity.RIGHT | Gravity.TOP;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 700, 0, 1000, 300);
assertRelFrame(w, 700, 0, 1000, 300);
w.mAttrs.gravity = Gravity.RIGHT | Gravity.BOTTOM;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 700, 700, 1000, 1000);
assertRelFrame(w, 700, 700, 1000, 1000);
// Window specified x and y are interpreted as offsets in the opposite
// direction of gravity
w.mAttrs.x = 100;
w.mAttrs.y = 100;
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, 600, 600, 900, 900);
assertRelFrame(w, 600, 600, 900, 900);
}
@@ -191,7 +194,7 @@ public class WindowFrameTests extends WindowTestsBase {
final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
final WindowFrames windowFrames = w.getWindowFrames();
windowFrames.setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
// For non fullscreen tasks the containing frame is based off the
// task bounds not the parent frame.
assertEquals(resolvedTaskBounds, w.getFrame());
@@ -204,7 +207,7 @@ public class WindowFrameTests extends WindowTestsBase {
final int cfBottom = logicalHeight / 2;
final Rect cf = new Rect(0, 0, cfRight, cfBottom);
windowFrames.setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertEquals(resolvedTaskBounds, w.getFrame());
assertEquals(0, w.getRelativeFrame().left);
assertEquals(0, w.getRelativeFrame().top);
@@ -233,7 +236,7 @@ public class WindowFrameTests extends WindowTestsBase {
final Rect pf = new Rect(0, 0, logicalWidth, logicalHeight);
final WindowFrames windowFrames = w.getWindowFrames();
windowFrames.setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
// For non fullscreen tasks the containing frame is based off the
// task bounds not the parent frame.
assertFrame(w, taskLeft, taskTop, taskRight, taskBottom);
@@ -249,7 +252,7 @@ public class WindowFrameTests extends WindowTestsBase {
task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
task.setBounds(null);
windowFrames.setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, cf);
}
@@ -274,7 +277,9 @@ public class WindowFrameTests extends WindowTestsBase {
imeFrame.top = 400;
imeSource.setFrame(imeFrame);
imeSource.setVisible(true);
- w.updateRequestedVisibility(state);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_IME, true);
+ w.setRequestedVisibilities(requestedVisibilities);
w.mAboveInsetsState.addSource(imeSource);
// With no insets or system decor all the frames incoming from PhoneWindowManager
@@ -285,7 +290,7 @@ public class WindowFrameTests extends WindowTestsBase {
final Rect winRect = new Rect(200, 200, 300, 500);
task.setBounds(winRect);
w.getWindowFrames().setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, winRect.left, imeFrame.top - winRect.height(), winRect.right, imeFrame.top);
// Now check that it won't get moved beyond the top
@@ -293,7 +298,7 @@ public class WindowFrameTests extends WindowTestsBase {
task.setBounds(winRect);
w.setBounds(winRect);
w.getWindowFrames().setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, winRect.left, 0, winRect.right, winRect.height());
// Now we have status bar. Check that it won't go into the status bar area.
@@ -301,14 +306,14 @@ public class WindowFrameTests extends WindowTestsBase {
statusBarFrame.bottom = 60;
state.getSource(ITYPE_STATUS_BAR).setFrame(statusBarFrame);
w.getWindowFrames().setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertFrame(w, winRect.left, statusBarFrame.bottom, winRect.right,
statusBarFrame.bottom + winRect.height());
// Check that it's moved back without ime insets
state.removeSource(ITYPE_IME);
w.getWindowFrames().setFrames(pf, pf);
- w.computeFrame();
+ w.computeFrame(mTestDisplayFrames);
assertEquals(winRect, w.getFrame());
}
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 d9aa871447be..a91298f73d08 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -56,6 +56,7 @@ import android.platform.test.annotations.Presubmit;
import android.view.IWindowSessionCallback;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowManager;
@@ -107,9 +108,9 @@ public class WindowManagerServiceTests extends WindowTestsBase {
Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
spyOn(mWm.mAtmService);
- mWm.handleTaskFocusChange(tappedTask);
+ mWm.handleTaskFocusChange(tappedTask, null /* window */);
- verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId);
+ verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId, null);
}
@Test
@@ -128,9 +129,9 @@ public class WindowManagerServiceTests extends WindowTestsBase {
Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
spyOn(mWm.mAtmService);
- mWm.handleTaskFocusChange(tappedTask);
+ mWm.handleTaskFocusChange(tappedTask, null /* window */);
- verify(mWm.mAtmService, never()).setFocusedTask(tappedTask.mTaskId);
+ verify(mWm.mAtmService, never()).setFocusedTask(tappedTask.mTaskId, null);
}
@Test
@@ -151,9 +152,9 @@ public class WindowManagerServiceTests extends WindowTestsBase {
Task tappedTask = createTaskInRootTask(tappedRootTask, 0 /* userId */);
spyOn(mWm.mAtmService);
- mWm.handleTaskFocusChange(tappedTask);
+ mWm.handleTaskFocusChange(tappedTask, null /* window */);
- verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId);
+ verify(mWm.mAtmService).setFocusedTask(tappedTask.mTaskId, null);
}
@Test
@@ -278,7 +279,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
.getWindowType(eq(windowContextToken));
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
- UserHandle.USER_SYSTEM, new InsetsState(), null, new InsetsState(),
+ UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
new InsetsSourceControl[0]);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
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 ab496cf34acc..75a87ba9e04d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -42,8 +42,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
-import static com.android.server.wm.Task.ActivityState.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
@@ -62,6 +61,7 @@ import static org.mockito.Mockito.clearInvocations;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IRequestFinishCallback;
import android.app.PictureInPictureParams;
@@ -70,7 +70,6 @@ import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -81,7 +80,9 @@ import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.IWindowContainerTransactionCallback;
import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
@@ -346,7 +347,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testDisplayAreaTransaction() {
removeGlobalMinSizeRestriction();
- final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
+ final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
testTransaction(displayArea);
}
@@ -364,7 +365,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
testSetWindowingMode(rootTask);
- final DisplayArea displayArea = new DisplayArea<>(mWm, ABOVE_TASKS, "DisplayArea");
+ final DisplayArea displayArea = mDisplayContent.getDefaultTaskDisplayArea();
displayArea.setWindowingMode(WINDOWING_MODE_FREEFORM);
testSetWindowingMode(displayArea);
}
@@ -542,6 +543,36 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ public void testSetAdjacentLaunchRoot() {
+ DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY);
+
+ final Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ dc, WINDOWING_MODE_MULTI_WINDOW, null);
+ final RunningTaskInfo info1 = task1.getTaskInfo();
+ final Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask(
+ dc, WINDOWING_MODE_MULTI_WINDOW, null);
+ final RunningTaskInfo info2 = task2.getTaskInfo();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setAdjacentRoots(info1.token, info2.token, false /* moveTogether */);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+ assertEquals(task1.getAdjacentTaskFragment(), task2);
+ assertEquals(task2.getAdjacentTaskFragment(), task1);
+
+ wct = new WindowContainerTransaction();
+ wct.setLaunchAdjacentFlagRoot(info1.token);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+ assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
+
+ task1.setAdjacentTaskFragment(null, false /* moveTogether */);
+ task2.setAdjacentTaskFragment(null, false /* moveTogether */);
+ wct = new WindowContainerTransaction();
+ wct.clearLaunchAdjacentFlagRoot(info1.token);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+ assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, null);
+ }
+
+ @Test
public void testTileAddRemoveChild() {
final StubOrganizer listener = new StubOrganizer();
mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener);
@@ -779,8 +810,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Override
public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { }
@Override
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) { }
+ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { }
@Override
public void copySplashScreenView(int taskId) { }
@Override
@@ -1259,21 +1289,42 @@ public class WindowOrganizerTests extends WindowTestsBase {
@Test
public void testStartTasksInTransaction() {
WindowContainerTransaction wct = new WindowContainerTransaction();
- Bundle testOptions = new Bundle();
- testOptions.putInt("test", 20);
+ ActivityOptions testOptions = ActivityOptions.makeBasic();
+ testOptions.setTransientLaunch();
wct.startTask(1, null /* options */);
- wct.startTask(2, testOptions);
- spyOn(mWm.mAtmService);
- doReturn(START_CANCELED).when(mWm.mAtmService).startActivityFromRecents(anyInt(), any());
+ wct.startTask(2, testOptions.toBundle());
+ spyOn(mWm.mAtmService.mTaskSupervisor);
+ doReturn(START_CANCELED).when(mWm.mAtmService.mTaskSupervisor).startActivityFromRecents(
+ anyInt(), anyInt(), anyInt(), any());
clearInvocations(mWm.mAtmService);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
- final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
- verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(1), bundleCaptor.capture());
- assertTrue(bundleCaptor.getValue().isEmpty());
+ verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents(
+ anyInt(), anyInt(), eq(1), any());
+
+ final ArgumentCaptor<SafeActivityOptions> optionsCaptor =
+ ArgumentCaptor.forClass(SafeActivityOptions.class);
+ verify(mWm.mAtmService.mTaskSupervisor, times(1)).startActivityFromRecents(
+ anyInt(), anyInt(), eq(2), optionsCaptor.capture());
+ assertTrue(optionsCaptor.getValue().getOriginalOptions().getTransientLaunch());
+ }
+
+ @Test
+ public void testResumeTopsWhenLeavingPinned() {
+ final ActivityRecord record = makePipableActivity();
+ final Task rootTask = record.getRootTask();
+
+ clearInvocations(mWm.mAtmService.mRootWindowContainer);
+ final WindowContainerTransaction t = new WindowContainerTransaction();
+ WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
+ t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
- verify(mWm.mAtmService, times(1)).startActivityFromRecents(eq(2), bundleCaptor.capture());
- assertEquals(20, bundleCaptor.getValue().getInt("test"));
+ clearInvocations(mWm.mAtmService.mRootWindowContainer);
+ t.setWindowingMode(wct, WINDOWING_MODE_FULLSCREEN);
+ mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
}
/**
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 ed18d26f8448..c56b6141a652 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -23,11 +23,18 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
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.ActivityRecord.State.PAUSED;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityRecord.State.STARTED;
+import static com.android.server.wm.ActivityRecord.State.STOPPED;
+import static com.android.server.wm.ActivityRecord.State.STOPPING;
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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -43,6 +50,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
@@ -311,17 +319,17 @@ public class WindowProcessControllerTests extends WindowTestsBase {
callbackResult[0] = 0;
activity.mVisibleRequested = false;
- activity.setState(Task.ActivityState.PAUSED, "test");
+ activity.setState(PAUSED, "test");
mWpc.computeOomAdjFromActivities(callback);
assertEquals(paused, callbackResult[0]);
callbackResult[0] = 0;
- activity.setState(Task.ActivityState.STOPPING, "test");
+ activity.setState(STOPPING, "test");
mWpc.computeOomAdjFromActivities(callback);
assertEquals(stopping, callbackResult[0]);
callbackResult[0] = 0;
- activity.setState(Task.ActivityState.STOPPED, "test");
+ activity.setState(STOPPED, "test");
mWpc.computeOomAdjFromActivities(callback);
assertEquals(other, callbackResult[0]);
}
@@ -332,25 +340,25 @@ public class WindowProcessControllerTests extends WindowTestsBase {
spyOn(tracker);
final ActivityRecord activity = createActivityRecord(mWpc);
activity.mVisibleRequested = true;
- activity.setState(Task.ActivityState.STARTED, "test");
+ activity.setState(STARTED, "test");
verify(tracker).onAnyActivityVisible(mWpc);
assertTrue(mWpc.hasVisibleActivities());
- activity.setState(Task.ActivityState.RESUMED, "test");
+ activity.setState(RESUMED, "test");
verify(tracker).onActivityResumedWhileVisible(mWpc);
assertTrue(tracker.hasResumedActivity(mWpc.mUid));
activity.makeFinishingLocked();
- activity.setState(Task.ActivityState.PAUSING, "test");
+ activity.setState(PAUSING, "test");
assertFalse(tracker.hasResumedActivity(mWpc.mUid));
assertTrue(mWpc.hasForegroundActivities());
activity.setVisibility(false);
activity.mVisibleRequested = false;
- activity.setState(Task.ActivityState.STOPPED, "test");
+ activity.setState(STOPPED, "test");
verify(tracker).onAllActivitiesInvisible(mWpc);
assertFalse(mWpc.hasVisibleActivities());
@@ -365,8 +373,9 @@ public class WindowProcessControllerTests extends WindowTestsBase {
public void testTopActivityUiModeChangeScheduleConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
activity.mVisibleRequested = true;
- doReturn(true).when(activity).setOverrideNightMode(anyInt());
- mWpc.updateNightModeForAllActivities(Configuration.UI_MODE_NIGHT_YES);
+ doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+ mWpc.updateAppSpecificSettingsForAllActivities(Configuration.UI_MODE_NIGHT_YES,
+ LocaleList.forLanguageTags("en-XA"));
verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
}
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 d88ac256be5c..e6ad68aafaec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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_SPLIT_SCREEN_PRIMARY;
import static android.view.InsetsState.ITYPE_IME;
@@ -85,6 +86,7 @@ import android.view.Gravity;
import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
+import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -437,9 +439,9 @@ public class WindowStateTests extends WindowTestsBase {
.setWindow(statusBar, null /* frameProvider */, null /* imeFrameProvider */);
mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
- final InsetsState state = new InsetsState();
- state.getSource(ITYPE_STATUS_BAR).setVisible(false);
- app.updateRequestedVisibility(state);
+ final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+ app.setRequestedVisibilities(requestedVisibilities);
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
.updateClientVisibility(app);
waitUntilHandlersIdle();
@@ -834,8 +836,7 @@ public class WindowStateTests extends WindowTestsBase {
WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
"SameTokenWindow");
mDisplayContent.setImeLayeringTarget(mAppWindow);
- sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
assertTrue(sameTokenWindow.needsRelativeLayeringToIme());
sameTokenWindow.removeImmediately();
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
@@ -847,8 +848,7 @@ public class WindowStateTests extends WindowTestsBase {
WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
mAppWindow.mToken, "SameTokenWindow");
mDisplayContent.setImeLayeringTarget(mAppWindow);
- sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
@@ -980,4 +980,19 @@ public class WindowStateTests extends WindowTestsBase {
assertNotNull(state.peekSource(ITYPE_IME));
assertTrue(state.getSource(ITYPE_IME).isVisible());
}
+
+ @Test
+ public void testRequestedVisibility() {
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ app.mActivityRecord.setVisible(false);
+ app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+ assertFalse(app.isVisibleRequested());
+
+ // It doesn't have a surface yet, but should still be visible requested.
+ app.setHasSurface(false);
+ app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */);
+
+ assertFalse(app.isVisible());
+ assertTrue(app.isVisibleRequested());
+ }
}
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 611b3f5e62a5..b997acfaadcf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
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_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -29,11 +30,13 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.os.Process.SYSTEM_UID;
import static android.view.View.VISIBLE;
+import static android.view.ViewRootImpl.INSETS_LAYOUT_GENERALIZATION;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -53,6 +56,7 @@ 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.StartingSurfaceController.DEBUG_ENABLE_SHELL_DRAWER;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
import static org.junit.Assert.assertEquals;
@@ -63,6 +67,7 @@ import static org.mockito.Mockito.mock;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
@@ -72,7 +77,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.os.Bundle;
@@ -83,10 +88,12 @@ import android.service.voice.IVoiceInteractionSession;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
import android.view.IWindow;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
@@ -94,6 +101,8 @@ import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.window.ITransitionPlayer;
import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -119,6 +128,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
// Default package name
static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
+ static final int DEFAULT_TASK_FRAGMENT_ORGANIZER_UID = 10000;
+ static final String DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME = "Test:TaskFragmentOrganizer";
+
// Default base activity name
private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
@@ -132,6 +144,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
DisplayInfo mDisplayInfo = new DisplayInfo();
DisplayContent mDefaultDisplay;
+ static final int STATUS_BAR_HEIGHT = 10;
+ static final int NAV_BAR_HEIGHT = 15;
+
/**
* It is {@link #mDefaultDisplay} by default. If the test class or method is annotated with
* {@link UseTestDisplay}, it will be an additional display.
@@ -185,6 +200,13 @@ class WindowTestsBase extends SystemServiceTestsBase {
SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
+ // Update the display policy to make the screen fully turned on so animation is allowed
+ final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy();
+ displayPolicy.screenTurnedOn(null /* screenOnListener */);
+ displayPolicy.finishKeyguardDrawn();
+ displayPolicy.finishWindowsDrawn();
+ displayPolicy.finishScreenTurningOn();
+
mTransaction = mSystemServicesTestRule.mTransaction;
mMockSession = mock(Session.class);
@@ -212,6 +234,10 @@ class WindowTestsBase extends SystemServiceTestsBase {
// {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
// may be set on some device form factors.
mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+ // Ensure letterbox reachability treatment isn't overridden on any device target.
+ // {@link com.android.internal.R.bool.config_letterboxIsReachabilityEnabled},
+ // may be set on some device form factors.
+ mAtm.mWindowManager.mLetterboxConfiguration.setIsReachabilityEnabled(false);
checkDeviceSpecificOverridesNotApplied();
}
@@ -219,12 +245,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
@After
public void tearDown() throws Exception {
// Revert back to device overrides.
- mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
- mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio));
- mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
- mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier));
+ mAtm.mWindowManager.mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+ mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ mAtm.mWindowManager.mLetterboxConfiguration.resetIsReachabilityEnabled();
}
/**
@@ -268,6 +291,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
if (addAll || ArrayUtils.contains(requestedWindows, W_STATUS_BAR)) {
mStatusBarWindow = createCommonWindow(null, TYPE_STATUS_BAR, "mStatusBarWindow");
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ mStatusBarWindow.mAttrs.height = STATUS_BAR_HEIGHT;
+ mStatusBarWindow.mAttrs.gravity = Gravity.TOP;
+ mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
+ LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mStatusBarWindow.setRequestedSize(WindowManager.LayoutParams.MATCH_PARENT,
+ STATUS_BAR_HEIGHT);
+ }
}
if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
mNotificationShadeWindow = createCommonWindow(null, TYPE_NOTIFICATION_SHADE,
@@ -275,6 +306,15 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
if (addAll || ArrayUtils.contains(requestedWindows, W_NAVIGATION_BAR)) {
mNavBarWindow = createCommonWindow(null, TYPE_NAVIGATION_BAR, "mNavBarWindow");
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ mNavBarWindow.mAttrs.height = NAV_BAR_HEIGHT;
+ mNavBarWindow.mAttrs.gravity = Gravity.BOTTOM;
+ mNavBarWindow.mAttrs.paramsForRotation = new WindowManager.LayoutParams[4];
+ for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+ mNavBarWindow.mAttrs.paramsForRotation[rot] =
+ getNavBarLayoutParamsForRotation(rot);
+ }
+ }
}
if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
mDockedDividerWindow = createCommonWindow(null, TYPE_DOCK_DIVIDER,
@@ -302,6 +342,37 @@ class WindowTestsBase extends SystemServiceTestsBase {
waitUntilHandlersIdle();
}
+ private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
+ int width = WindowManager.LayoutParams.MATCH_PARENT;
+ int height = WindowManager.LayoutParams.MATCH_PARENT;
+ int gravity = Gravity.BOTTOM;
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ switch (rotation) {
+ case ROTATION_UNDEFINED:
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_180:
+ height = NAV_BAR_HEIGHT;
+ break;
+ case Surface.ROTATION_90:
+ gravity = Gravity.RIGHT;
+ width = NAV_BAR_HEIGHT;
+ break;
+ case Surface.ROTATION_270:
+ gravity = Gravity.LEFT;
+ width = NAV_BAR_HEIGHT;
+ break;
+ }
+ }
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR);
+ lp.width = width;
+ lp.height = height;
+ if (INSETS_LAYOUT_GENERALIZATION) {
+ lp.gravity = gravity;
+ }
+ return lp;
+ }
+
void beforeCreateTestDisplay() {
// Called before display is created.
}
@@ -533,7 +604,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
Task createTaskInRootTask(Task rootTask, int userId) {
final Task task = new TaskBuilder(rootTask.mTaskSupervisor)
.setUserId(userId)
- .setParentTask(rootTask)
+ .setParentTaskFragment(rootTask)
.build();
return task;
}
@@ -619,6 +690,35 @@ class WindowTestsBase extends SystemServiceTestsBase {
activity.mVisibleRequested = true;
}
+ /**
+ * Creates a {@link TaskFragment} and attach it to the {@code parentTask}.
+ *
+ * @param parentTask the {@link Task} this TaskFragment is going to be attached
+ * @param createEmbeddedTask Sets to {@code true} to create an embedded Task for this
+ * TaskFragment. Otherwise, create a {@link ActivityRecord}.
+ * @return the created TaskFragment
+ */
+ static TaskFragment createTaskFragmentWithParentTask(@NonNull Task parentTask,
+ boolean createEmbeddedTask) {
+ final TaskFragmentBuilder builder = new TaskFragmentBuilder(parentTask.mAtmService)
+ .setParentTask(parentTask);
+ if (createEmbeddedTask) {
+ builder.createEmbeddedTask();
+ } else {
+ builder.createActivityCount(1);
+ }
+ return builder.build();
+ }
+
+ static TaskFragment createTaskFragmentWithEmbeddedActivity(@NonNull Task parentTask,
+ TaskFragmentOrganizer organizer) {
+ return new TaskFragmentBuilder(parentTask.mAtmService)
+ .setParentTask(parentTask)
+ .createActivityCount(1)
+ .setOrganizer(organizer)
+ .build();
+ }
+
/** Creates a {@link DisplayContent} that supports IME and adds it to the system. */
DisplayContent createNewDisplay() {
return createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
@@ -700,6 +800,23 @@ class WindowTestsBase extends SystemServiceTestsBase {
};
}
+ BLASTSyncEngine createTestBLASTSyncEngine() {
+ return new BLASTSyncEngine(mWm) {
+ @Override
+ void scheduleTimeout(SyncGroup s, long timeoutMs) {
+ // Disable timeout.
+ }
+ };
+ }
+
+ /** Sets up a simple implementation of transition player for shell transitions. */
+ TestTransitionPlayer registerTestTransitionPlayer() {
+ final TestTransitionPlayer testPlayer = new TestTransitionPlayer(
+ mAtm.getTransitionController(), mAtm.mWindowOrganizerController);
+ testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */);
+ return testPlayer;
+ }
+
/**
* Avoids rotating screen disturbed by some conditions. It is usually used for the default
* display that is not the instance of {@link TestDisplayContent} (it bypasses the conditions).
@@ -772,6 +889,21 @@ class WindowTestsBase extends SystemServiceTestsBase {
mAtm.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
}
+ /** Mocks the behavior of taking a snapshot. */
+ void mockSurfaceFreezerSnapshot(SurfaceFreezer surfaceFreezer) {
+ final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+ mock(SurfaceControl.ScreenshotHardwareBuffer.class);
+ final HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
+ spyOn(surfaceFreezer);
+ doReturn(screenshotBuffer).when(surfaceFreezer)
+ .createSnapshotBufferInner(any(), any());
+ doReturn(null).when(surfaceFreezer)
+ .createFromHardwareBufferInner(any());
+ doReturn(hardwareBuffer).when(screenshotBuffer).getHardwareBuffer();
+ doReturn(100).when(hardwareBuffer).getWidth();
+ doReturn(100).when(hardwareBuffer).getHeight();
+ }
+
/**
* Builder for creating new activities.
*/
@@ -992,7 +1124,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
// Apply the root activity info and intent
.setActivityInfo(aInfo)
.setIntent(intent)
- .setParentTask(mParentTask).build();
+ .setParentTaskFragment(mParentTask).build();
} else if (mTask == null && mParentTask != null && DisplayContent.alwaysCreateRootTask(
mParentTask.getWindowingMode(), mParentTask.getActivityType())) {
// The parent task can be the task root.
@@ -1052,6 +1184,82 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
}
+ static class TaskFragmentBuilder {
+ private final ActivityTaskManagerService mAtm;
+ private Task mParentTask;
+ private boolean mCreateParentTask;
+ private boolean mCreateEmbeddedTask;
+ private int mCreateActivityCount = 0;
+ @Nullable
+ private TaskFragmentOrganizer mOrganizer;
+ private IBinder mFragmentToken;
+
+ TaskFragmentBuilder(ActivityTaskManagerService service) {
+ mAtm = service;
+ }
+
+ TaskFragmentBuilder setCreateParentTask() {
+ mCreateParentTask = true;
+ return this;
+ }
+
+ TaskFragmentBuilder setParentTask(Task task) {
+ mParentTask = task;
+ return this;
+ }
+
+ /** Creates a child embedded Task and its Activity */
+ TaskFragmentBuilder createEmbeddedTask() {
+ mCreateEmbeddedTask = true;
+ return this;
+ }
+
+ TaskFragmentBuilder createActivityCount(int count) {
+ mCreateActivityCount = count;
+ return this;
+ }
+
+ TaskFragmentBuilder setOrganizer(@Nullable TaskFragmentOrganizer organizer) {
+ mOrganizer = organizer;
+ return this;
+ }
+
+ TaskFragmentBuilder setFragmentToken(@Nullable IBinder fragmentToken) {
+ mFragmentToken = fragmentToken;
+ return this;
+ }
+
+ TaskFragment build() {
+ SystemServicesTestRule.checkHoldsLock(mAtm.mGlobalLock);
+
+ final TaskFragment taskFragment = new TaskFragment(mAtm, mFragmentToken,
+ mOrganizer != null);
+ if (mParentTask == null && mCreateParentTask) {
+ mParentTask = new TaskBuilder(mAtm.mTaskSupervisor).build();
+ }
+ if (mParentTask != null) {
+ mParentTask.addChild(taskFragment, POSITION_TOP);
+ }
+ if (mCreateEmbeddedTask) {
+ new TaskBuilder(mAtm.mTaskSupervisor)
+ .setParentTaskFragment(taskFragment)
+ .setCreateActivity(true)
+ .build();
+ }
+ while (mCreateActivityCount > 0) {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).build();
+ taskFragment.addChild(activity);
+ mCreateActivityCount--;
+ }
+ if (mOrganizer != null) {
+ taskFragment.setTaskFragmentOrganizer(
+ mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID,
+ DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+ }
+ return taskFragment;
+ }
+ }
+
/**
* Builder for creating new tasks.
*/
@@ -1072,7 +1280,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
private IVoiceInteractionSession mVoiceSession;
private boolean mCreateParentTask = false;
- private Task mParentTask;
+ private TaskFragment mParentTaskFragment;
private boolean mCreateActivity = false;
@@ -1156,8 +1364,8 @@ class WindowTestsBase extends SystemServiceTestsBase {
return this;
}
- TaskBuilder setParentTask(Task parentTask) {
- mParentTask = parentTask;
+ TaskBuilder setParentTaskFragment(TaskFragment parentTaskFragment) {
+ mParentTaskFragment = parentTaskFragment;
return this;
}
@@ -1170,12 +1378,13 @@ class WindowTestsBase extends SystemServiceTestsBase {
SystemServicesTestRule.checkHoldsLock(mSupervisor.mService.mGlobalLock);
// Create parent task.
- if (mParentTask == null && mCreateParentTask) {
- mParentTask = mTaskDisplayArea.createRootTask(
+ if (mParentTaskFragment == null && mCreateParentTask) {
+ mParentTaskFragment = mTaskDisplayArea.createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
}
- if (mParentTask != null && !Mockito.mockingDetails(mParentTask).isSpy()) {
- spyOn(mParentTask);
+ if (mParentTaskFragment != null
+ && !Mockito.mockingDetails(mParentTaskFragment).isSpy()) {
+ spyOn(mParentTaskFragment);
}
// Create task.
@@ -1203,13 +1412,15 @@ class WindowTestsBase extends SystemServiceTestsBase {
.setOnTop(mOnTop)
.setVoiceSession(mVoiceSession);
final Task task;
- if (mParentTask == null) {
+ if (mParentTaskFragment == null) {
task = builder.setActivityType(mActivityType)
.setParent(mTaskDisplayArea)
.build();
} else {
- task = builder.setParent(mParentTask).build();
- mParentTask.moveToFront("build-task");
+ task = builder.setParent(mParentTaskFragment).build();
+ if (mParentTaskFragment.asTask() != null) {
+ mParentTaskFragment.asTask().moveToFront("build-task");
+ }
}
spyOn(task);
task.mUserId = mUserId;
@@ -1290,12 +1501,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
}
@Override
- public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
- boolean playRevealAnimation) {
+ public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
synchronized (mWMService.mGlobalLock) {
- final IBinder appToken = mTaskAppMap.get(taskId);
+ final IBinder appToken = mTaskAppMap.get(removalInfo.taskId);
if (appToken != null) {
- mTaskAppMap.remove(taskId);
+ mTaskAppMap.remove(removalInfo.taskId);
final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
appToken);
WindowState win = mAppWindowMap.remove(appToken);
@@ -1431,7 +1641,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
}
- class TestTransitionPlayer extends ITransitionPlayer.Stub {
+ static class TestTransitionPlayer extends ITransitionPlayer.Stub {
final TransitionController mController;
final WindowOrganizerController mOrganizer;
Transition mLastTransit = null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index d967891fdb76..4dffe7e8345b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -20,9 +20,10 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
@@ -37,10 +38,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
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.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowStateAnimator.PRESERVED_SURFACE_LAYER;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -293,6 +297,7 @@ public class ZOrderingTests extends WindowTestsBase {
final WindowState appAboveImeTarget = createWindow("appAboveImeTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
+ mDisplayContent.setImeControlTarget(imeAppTarget);
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows except for non-fullscreen app window above it and
@@ -339,6 +344,7 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testAssignWindowLayers_ForStatusBarImeTarget() {
mDisplayContent.setImeLayeringTarget(mStatusBarWindow);
+ mDisplayContent.setImeControlTarget(mStatusBarWindow);
mDisplayContent.assignChildLayers(mTransaction);
assertWindowHigher(mImeWindow, mChildAppWindowAbove);
@@ -399,6 +405,50 @@ public class ZOrderingTests extends WindowTestsBase {
}
@Test
+ public void testAssignWindowLayers_ForImeOnAppWithRecentsAnimating() {
+ final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
+ mAppWindow.mActivityRecord, "imeAppTarget");
+ mDisplayContent.setImeInputTarget(imeAppTarget);
+ mDisplayContent.setImeLayeringTarget(imeAppTarget);
+ mDisplayContent.setImeControlTarget(imeAppTarget);
+ mDisplayContent.updateImeParent();
+
+ // Simulate the ime layering target task is animating with recents animation.
+ final Task imeAppTargetTask = imeAppTarget.getTask();
+ final SurfaceAnimator imeTargetTaskAnimator = imeAppTargetTask.mSurfaceAnimator;
+ spyOn(imeTargetTaskAnimator);
+ doReturn(ANIMATION_TYPE_RECENTS).when(imeTargetTaskAnimator).getAnimationType();
+ doReturn(true).when(imeTargetTaskAnimator).isAnimating();
+
+ mDisplayContent.assignChildLayers(mTransaction);
+
+ // Ime should on top of the application window when in recents animation and keep
+ // attached on app.
+ assertTrue(mDisplayContent.shouldImeAttachedToApp());
+ assertWindowHigher(mImeWindow, imeAppTarget);
+ }
+
+ @Test
+ public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
+ final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
+ mAppWindow.mActivityRecord, "imeAppTarget");
+ mDisplayContent.setImeInputTarget(imeAppTarget);
+ mDisplayContent.setImeLayeringTarget(imeAppTarget);
+ mDisplayContent.setImeControlTarget(imeAppTarget);
+
+ // Set a popup IME layering target and keeps the original IME control target behinds it.
+ final WindowState popupImeTargetWin = createWindow(imeAppTarget,
+ TYPE_APPLICATION_SUB_PANEL, mAppWindow.mActivityRecord, "popupImeTargetWin");
+ mDisplayContent.setImeLayeringTarget(popupImeTargetWin);
+ mDisplayContent.updateImeParent();
+
+ // Ime should on top of the popup IME layering target window.
+ mDisplayContent.assignChildLayers(mTransaction);
+ assertWindowHigher(mImeWindow, popupImeTargetWin);
+ }
+
+
+ @Test
public void testAssignWindowLayers_ForNegativelyZOrderedSubtype() {
// TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
// then we can drop all negative layering on the windowing side.
@@ -440,24 +490,73 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testDockedDividerPosition() {
- final WindowState pinnedStackWindow = createWindow(null, WINDOWING_MODE_PINNED,
- ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
- "pinnedStackWindow");
- final WindowState splitScreenWindow = createWindow(null,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
- mDisplayContent, "splitScreenWindow");
- final WindowState splitScreenSecondaryWindow = createWindow(null,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
- TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
- final WindowState assistantStackWindow = createWindow(null,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
- mDisplayContent, "assistantStackWindow");
+ final Task pinnedTask =
+ createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ final WindowState pinnedWindow =
+ createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
+
+ final Task belowTask =
+ createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final WindowState belowTaskWindow =
+ createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
+
+ final Task splitScreenTask1 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final WindowState splitWindow1 =
+ createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
+ final Task splitScreenTask2 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final WindowState splitWindow2 =
+ createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
+ splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
+ splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
+
+ final Task aboveTask =
+ createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final WindowState aboveTaskWindow =
+ createAppWindow(aboveTask, ACTIVITY_TYPE_STANDARD, "aboveTaskWindow");
mDisplayContent.assignChildLayers(mTransaction);
- assertWindowHigher(mDockedDividerWindow, splitScreenWindow);
- assertWindowHigher(mDockedDividerWindow, splitScreenSecondaryWindow);
- assertWindowHigher(pinnedStackWindow, mDockedDividerWindow);
+ assertWindowHigher(splitWindow1, belowTaskWindow);
+ assertWindowHigher(splitWindow2, belowTaskWindow);
+ assertWindowHigher(mDockedDividerWindow, splitWindow1);
+ assertWindowHigher(mDockedDividerWindow, splitWindow2);
+ assertWindowHigher(aboveTaskWindow, mDockedDividerWindow);
+ assertWindowHigher(pinnedWindow, aboveTaskWindow);
+ }
+
+
+ @Test
+ public void testDockedDividerPosition_noAboveTask() {
+ final Task pinnedTask =
+ createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ final WindowState pinnedWindow =
+ createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
+
+ final Task belowTask =
+ createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+ final WindowState belowTaskWindow =
+ createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
+
+ final Task splitScreenTask1 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final WindowState splitWindow1 =
+ createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
+ final Task splitScreenTask2 =
+ createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ final WindowState splitWindow2 =
+ createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
+ splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
+ splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
+
+ mDisplayContent.assignChildLayers(mTransaction);
+
+ assertWindowHigher(splitWindow1, belowTaskWindow);
+ assertWindowHigher(splitWindow2, belowTaskWindow);
+ assertWindowHigher(mDockedDividerWindow, splitWindow1);
+ assertWindowHigher(mDockedDividerWindow, splitWindow2);
+ assertWindowHigher(pinnedWindow, mDockedDividerWindow);
}
@Test
@@ -493,4 +592,27 @@ public class ZOrderingTests extends WindowTestsBase {
assertZOrderGreaterThan(mTransaction, mNavBarWindow.mToken.getSurfaceControl(),
mDisplayContent.getImeContainer().getSurfaceControl());
}
+
+ @Test
+ public void testPopupWindowAndParentIsImeTarget_expectHigherThanIme_inMultiWindow() {
+ // Simulate the app window is in multi windowing mode and being IME target
+ mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW);
+ mDisplayContent.setImeLayeringTarget(mAppWindow);
+ mDisplayContent.setImeInputTarget(mAppWindow);
+
+ // Create a popupWindow
+ assertWindowHigher(mImeWindow, mAppWindow);
+ final WindowState popupWindow = createWindow(mAppWindow, TYPE_APPLICATION_PANEL,
+ mDisplayContent, "PopupWindow");
+ spyOn(popupWindow);
+
+ mDisplayContent.assignChildLayers(mTransaction);
+
+ // Verify the surface layer of the popupWindow should higher than IME
+ verify(popupWindow).needsRelativeLayeringToIme();
+ assertThat(popupWindow.needsRelativeLayeringToIme()).isTrue();
+ assertZOrderGreaterThan(mTransaction, popupWindow.getSurfaceControl(),
+ mDisplayContent.getImeContainer().getSurfaceControl());
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index ba6fe4812793..7f151265380e 100755
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -177,10 +177,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
// Delay for debouncing USB disconnects.
// We often get rapid connect/disconnect events when enabling USB functions,
// which need debouncing.
- private static final int DEVICE_STATE_UPDATE_DELAY = 3000;
-
- // Delay for debouncing USB disconnects on Type-C ports in host mode
- private static final int HOST_STATE_UPDATE_DELAY = 1000;
+ private static final int UPDATE_DELAY = 1000;
// Timeout for entering USB request mode.
// Request is cancelled if host does not configure device within 10 seconds.
@@ -642,7 +639,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
msg.arg1 = connected;
msg.arg2 = configured;
// debounce disconnects to avoid problems bringing up USB tethering
- sendMessageDelayed(msg, (connected == 0) ? DEVICE_STATE_UPDATE_DELAY : 0);
+ sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
}
public void updateHostState(UsbPort port, UsbPortStatus status) {
@@ -657,7 +654,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
removeMessages(MSG_UPDATE_PORT_STATE);
Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args);
// debounce rapid transitions of connect/disconnect on type-c ports
- sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
+ sendMessageDelayed(msg, UPDATE_DELAY);
}
private void setAdbEnabled(boolean enable) {
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 0c65cc40bd82..286cff90daab 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -688,6 +688,8 @@ class UsbUserPermissionManager {
String packageName,
PendingIntent pi,
int uid) {
+ boolean throwException = false;
+
// compare uid with packageName to foil apps pretending to be someone else
try {
ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
@@ -695,11 +697,13 @@ class UsbUserPermissionManager {
Slog.w(TAG, "package " + packageName
+ " does not match caller's uid " + uid);
EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, "");
- throw new IllegalArgumentException("package " + packageName
- + " not found");
+ throwException = true;
}
} catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("package " + packageName + " not found");
+ throwException = true;
+ } finally {
+ if (throwException)
+ throw new IllegalArgumentException("package " + packageName + " not found");
}
requestPermissionDialog(device, accessory, canBeDefault, packageName, uid, mContext, pi);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 4dc83ae98d89..36bb375be3c8 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -838,7 +838,7 @@ final class HotwordDetectionConnection {
try {
return mContext.bindIsolatedService(
mIntent,
- Context.BIND_AUTO_CREATE | mBindingFlags,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
"hotword_detector_" + mInstanceNumber,
mExecutor,
serviceConnection);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 9ea2b7b12ad0..8445ed4884e2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -167,6 +167,16 @@ public class VoiceInteractionManagerService extends SystemService {
public void onStart() {
publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub);
publishLocalService(VoiceInteractionManagerInternal.class, new LocalService());
+ mAmInternal.setVoiceInteractionManagerProvider(
+ new ActivityManagerInternal.VoiceInteractionManagerProvider() {
+ @Override
+ public void notifyActivityEventChanged() {
+ if (DEBUG) {
+ Slog.d(TAG, "call notifyActivityEventChanged");
+ }
+ mServiceStub.notifyActivityEventChanged();
+ }
+ });
}
@Override
@@ -386,6 +396,14 @@ public class VoiceInteractionManagerService extends SystemService {
return mImpl.supportsLocalVoiceInteraction();
}
+ void notifyActivityEventChanged() {
+ synchronized (this) {
+ if (mImpl == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mImpl.notifyActivityEventChangedLocked());
+ }
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -1109,6 +1127,40 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
+ @Override
+ public void startListeningVisibleActivityChanged(@NonNull IBinder token) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "startListeningVisibleActivityChanged without running"
+ + " voice interaction service");
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.startListeningVisibleActivityChangedLocked(token);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public void stopListeningVisibleActivityChanged(@NonNull IBinder token) {
+ synchronized (this) {
+ if (mImpl == null) {
+ Slog.w(TAG, "stopListeningVisibleActivityChanged without running"
+ + " voice interaction service");
+ return;
+ }
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ mImpl.stopListeningVisibleActivityChangedLocked(token);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
//----------------- Hotword Detection/Validation APIs --------------------------------//
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 558a9ac9298e..52c5b6bbc239 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -414,6 +414,44 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return mInfo.getSupportsLocalInteraction();
}
+ public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
+ if (DEBUG) {
+ Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
+ }
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "startListeningVisibleActivityChangedLocked does not match"
+ + " active session");
+ return;
+ }
+ mActiveSession.startListeningVisibleActivityChangedLocked();
+ }
+
+ public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
+ if (DEBUG) {
+ Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
+ }
+ if (mActiveSession == null || token != mActiveSession.mToken) {
+ Slog.w(TAG, "stopListeningVisibleActivityChangedLocked does not match"
+ + " active session");
+ return;
+ }
+ mActiveSession.stopListeningVisibleActivityChangedLocked();
+ }
+
+ public void notifyActivityEventChangedLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityEventChangedLocked");
+ }
+ if (mActiveSession == null || !mActiveSession.mShown) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityEventChangedLocked not allowed on no session or"
+ + " hidden session");
+ }
+ return;
+ }
+ mActiveSession.notifyActivityEventChangedLocked();
+ }
+
public void updateStateLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 08e9703124ab..90ccec852e1e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -56,6 +56,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.service.voice.IVoiceInteractionSessionService;
+import android.service.voice.VisibleActivityInfo;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionSession;
import android.util.Slog;
@@ -71,16 +72,20 @@ import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.ActivityAssistInfo;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.PrintWriter;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
final class VoiceInteractionSessionConnection implements ServiceConnection,
AssistDataRequesterCallbacks {
static final String TAG = "VoiceInteractionServiceManager";
+ static final boolean DEBUG = false;
static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
System.getProperty("vendor.powerhal.interaction.max", "200"));
static final int BOOST_TIMEOUT_MS = 300;
@@ -114,6 +119,10 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
+ private boolean mListeningVisibleActivity;
+ private final ScheduledExecutorService mScheduledExecutorService =
+ Executors.newSingleThreadScheduledExecutor();
+ private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>();
private final PowerManagerInternal mPowerManagerInternal;
private PowerBoostSetter mSetPowerBoostRunnable;
private final Handler mFgHandler;
@@ -496,6 +505,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
}
public void cancelLocked(boolean finishTask) {
+ mListeningVisibleActivity = false;
+ mVisibleActivityInfos.clear();
hideLocked();
mCanceled = true;
if (mBound) {
@@ -569,6 +580,156 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
mPendingShowCallbacks.clear();
}
+ void startListeningVisibleActivityChangedLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "startListeningVisibleActivityChangedLocked");
+ }
+ mListeningVisibleActivity = true;
+ mVisibleActivityInfos.clear();
+
+ mScheduledExecutorService.execute(() -> {
+ if (DEBUG) {
+ Slog.d(TAG, "call updateVisibleActivitiesLocked from enable listening");
+ }
+ synchronized (mLock) {
+ updateVisibleActivitiesLocked();
+ }
+ });
+ }
+
+ void stopListeningVisibleActivityChangedLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "stopListeningVisibleActivityChangedLocked");
+ }
+ mListeningVisibleActivity = false;
+ mVisibleActivityInfos.clear();
+ }
+
+ void notifyActivityEventChangedLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyActivityEventChangedLocked");
+ }
+ if (!mListeningVisibleActivity) {
+ if (DEBUG) {
+ Slog.d(TAG, "not enable listening visible activity");
+ }
+ return;
+ }
+ mScheduledExecutorService.execute(() -> {
+ if (DEBUG) {
+ Slog.d(TAG, "call updateVisibleActivitiesLocked from activity event");
+ }
+ synchronized (mLock) {
+ updateVisibleActivitiesLocked();
+ }
+ });
+ }
+
+ private List<VisibleActivityInfo> getVisibleActivityInfosLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "getVisibleActivityInfosLocked");
+ }
+ List<ActivityAssistInfo> allVisibleActivities =
+ LocalServices.getService(ActivityTaskManagerInternal.class)
+ .getTopVisibleActivities();
+ if (DEBUG) {
+ Slog.d(TAG,
+ "getVisibleActivityInfosLocked: allVisibleActivities=" + allVisibleActivities);
+ }
+ if (allVisibleActivities == null || allVisibleActivities.isEmpty()) {
+ Slog.w(TAG, "no visible activity");
+ return null;
+ }
+ final int count = allVisibleActivities.size();
+ final List<VisibleActivityInfo> visibleActivityInfos = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ ActivityAssistInfo info = allVisibleActivities.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, " : activityToken=" + info.getActivityToken()
+ + ", assistToken=" + info.getAssistToken()
+ + ", taskId=" + info.getTaskId());
+ }
+ visibleActivityInfos.add(
+ new VisibleActivityInfo(info.getTaskId(), info.getAssistToken()));
+ }
+ return visibleActivityInfos;
+ }
+
+ private void updateVisibleActivitiesLocked() {
+ if (DEBUG) {
+ Slog.d(TAG, "updateVisibleActivitiesLocked");
+ }
+ if (mSession == null) {
+ return;
+ }
+ if (!mShown || !mListeningVisibleActivity || mCanceled) {
+ return;
+ }
+ final List<VisibleActivityInfo> newVisibleActivityInfos = getVisibleActivityInfosLocked();
+
+ if (newVisibleActivityInfos == null || newVisibleActivityInfos.isEmpty()) {
+ updateVisibleActivitiesChangedLocked(mVisibleActivityInfos,
+ VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ mVisibleActivityInfos.clear();
+ return;
+ }
+ if (mVisibleActivityInfos.isEmpty()) {
+ updateVisibleActivitiesChangedLocked(newVisibleActivityInfos,
+ VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+ mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+ return;
+ }
+
+ final List<VisibleActivityInfo> addedActivities = new ArrayList<>();
+ final List<VisibleActivityInfo> removedActivities = new ArrayList<>();
+
+ removedActivities.addAll(mVisibleActivityInfos);
+ for (int i = 0; i < newVisibleActivityInfos.size(); i++) {
+ final VisibleActivityInfo candidateVisibleActivityInfo = newVisibleActivityInfos.get(i);
+ if (!removedActivities.isEmpty() && removedActivities.contains(
+ candidateVisibleActivityInfo)) {
+ removedActivities.remove(candidateVisibleActivityInfo);
+ } else {
+ addedActivities.add(candidateVisibleActivityInfo);
+ }
+ }
+
+ if (!addedActivities.isEmpty()) {
+ updateVisibleActivitiesChangedLocked(addedActivities,
+ VisibleActivityInfo.TYPE_ACTIVITY_ADDED);
+ }
+ if (!removedActivities.isEmpty()) {
+ updateVisibleActivitiesChangedLocked(removedActivities,
+ VisibleActivityInfo.TYPE_ACTIVITY_REMOVED);
+ }
+
+ mVisibleActivityInfos.clear();
+ mVisibleActivityInfos.addAll(newVisibleActivityInfos);
+ }
+
+ private void updateVisibleActivitiesChangedLocked(
+ List<VisibleActivityInfo> visibleActivityInfos, int type) {
+ if (visibleActivityInfos == null || visibleActivityInfos.isEmpty()) {
+ return;
+ }
+ if (mSession == null) {
+ return;
+ }
+ try {
+ for (int i = 0; i < visibleActivityInfos.size(); i++) {
+ mSession.updateVisibleActivityInfo(visibleActivityInfos.get(i), type);
+ }
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "updateVisibleActivitiesChangedLocked RemoteException : " + e);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "updateVisibleActivitiesChangedLocked type=" + type + ", count="
+ + visibleActivityInfos.size());
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {